diff --git a/.editorconfig b/.editorconfig index 1966f91763..140cc085c5 100644 --- a/.editorconfig +++ b/.editorconfig @@ -1,14 +1,901 @@ -# For ktlint configuration. Ref: https://ktlint.github.io/ - -[*.{kt,kts}] -# possible values: number (e.g. 2), "unset" (makes ktlint ignore indentation completely) -indent_size=unset -# true (recommended) / false -insert_final_newline=true -# possible values: number (e.g. 120) (package name, imports & comments are ignored), "off" -# it's automatically set to 100 on `ktlint --android ...` (per Android Kotlin Style Guide) -max_line_length=off - -# From https://github.com/pinterest/ktlint#custom-ktlint-specific-editorconfig-properties -# default IntelliJ IDEA style, same as alphabetical, but with "java", "javax", "kotlin" and alias imports in the end of the imports list -ij_kotlin_imports_layout=*,java.**,javax.**,kotlin.**,^ +[*] +charset = utf-8 +end_of_line = lf +indent_size = 4 +indent_style = space +insert_final_newline = true +max_line_length = 160 +tab_width = 4 +ij_continuation_indent_size = 8 +ij_formatter_off_tag = @formatter:off +ij_formatter_on_tag = @formatter:on +ij_formatter_tags_enabled = false +ij_smart_tabs = false +ij_visual_guides = none +ij_wrap_on_typing = false + +[*.java] +ij_java_align_consecutive_assignments = false +ij_java_align_consecutive_variable_declarations = false +ij_java_align_group_field_declarations = false +ij_java_align_multiline_annotation_parameters = false +ij_java_align_multiline_array_initializer_expression = false +ij_java_align_multiline_assignment = false +ij_java_align_multiline_binary_operation = false +ij_java_align_multiline_chained_methods = false +ij_java_align_multiline_extends_list = false +ij_java_align_multiline_for = true +ij_java_align_multiline_method_parentheses = false +ij_java_align_multiline_parameters = true +ij_java_align_multiline_parameters_in_calls = false +ij_java_align_multiline_parenthesized_expression = false +ij_java_align_multiline_records = true +ij_java_align_multiline_resources = true +ij_java_align_multiline_ternary_operation = false +ij_java_align_multiline_text_blocks = false +ij_java_align_multiline_throws_list = false +ij_java_align_subsequent_simple_methods = false +ij_java_align_throws_keyword = false +ij_java_annotation_parameter_wrap = off +ij_java_array_initializer_new_line_after_left_brace = false +ij_java_array_initializer_right_brace_on_new_line = false +ij_java_array_initializer_wrap = off +ij_java_assert_statement_colon_on_next_line = false +ij_java_assert_statement_wrap = off +ij_java_assignment_wrap = off +ij_java_binary_operation_sign_on_next_line = false +ij_java_binary_operation_wrap = off +ij_java_blank_lines_after_anonymous_class_header = 0 +ij_java_blank_lines_after_class_header = 0 +ij_java_blank_lines_after_imports = 1 +ij_java_blank_lines_after_package = 1 +ij_java_blank_lines_around_class = 1 +ij_java_blank_lines_around_field = 0 +ij_java_blank_lines_around_field_in_interface = 0 +ij_java_blank_lines_around_initializer = 1 +ij_java_blank_lines_around_method = 1 +ij_java_blank_lines_around_method_in_interface = 1 +ij_java_blank_lines_before_class_end = 0 +ij_java_blank_lines_before_imports = 1 +ij_java_blank_lines_before_method_body = 0 +ij_java_blank_lines_before_package = 0 +ij_java_block_brace_style = end_of_line +ij_java_block_comment_at_first_column = true +ij_java_builder_methods = none +ij_java_call_parameters_new_line_after_left_paren = false +ij_java_call_parameters_right_paren_on_new_line = false +ij_java_call_parameters_wrap = off +ij_java_case_statement_on_separate_line = true +ij_java_catch_on_new_line = false +ij_java_class_annotation_wrap = split_into_lines +ij_java_class_brace_style = end_of_line +ij_java_class_count_to_use_import_on_demand = 99 +ij_java_class_names_in_javadoc = 1 +ij_java_do_not_indent_top_level_class_members = false +ij_java_do_not_wrap_after_single_annotation = false +ij_java_do_while_brace_force = never +ij_java_doc_add_blank_line_after_description = true +ij_java_doc_add_blank_line_after_param_comments = false +ij_java_doc_add_blank_line_after_return = false +ij_java_doc_add_p_tag_on_empty_lines = true +ij_java_doc_align_exception_comments = true +ij_java_doc_align_param_comments = true +ij_java_doc_do_not_wrap_if_one_line = false +ij_java_doc_enable_formatting = true +ij_java_doc_enable_leading_asterisks = true +ij_java_doc_indent_on_continuation = false +ij_java_doc_keep_empty_lines = true +ij_java_doc_keep_empty_parameter_tag = true +ij_java_doc_keep_empty_return_tag = true +ij_java_doc_keep_empty_throws_tag = true +ij_java_doc_keep_invalid_tags = true +ij_java_doc_param_description_on_new_line = false +ij_java_doc_preserve_line_breaks = false +ij_java_doc_use_throws_not_exception_tag = true +ij_java_else_on_new_line = false +ij_java_enum_constants_wrap = off +ij_java_extends_keyword_wrap = off +ij_java_extends_list_wrap = off +ij_java_field_annotation_wrap = split_into_lines +ij_java_finally_on_new_line = false +ij_java_for_brace_force = never +ij_java_for_statement_new_line_after_left_paren = false +ij_java_for_statement_right_paren_on_new_line = false +ij_java_for_statement_wrap = off +ij_java_generate_final_locals = false +ij_java_generate_final_parameters = false +ij_java_if_brace_force = never +ij_java_imports_layout = $android.**,$androidx.**,$com.**,$junit.**,$net.**,$org.**,$java.**,$javax.**,$*,|,android.**,|,androidx.**,|,com.**,|,junit.**,|,net.**,|,org.**,|,java.**,|,javax.**,|,*,| +ij_java_indent_case_from_switch = true +ij_java_insert_inner_class_imports = false +ij_java_insert_override_annotation = true +ij_java_keep_blank_lines_before_right_brace = 2 +ij_java_keep_blank_lines_between_package_declaration_and_header = 2 +ij_java_keep_blank_lines_in_code = 2 +ij_java_keep_blank_lines_in_declarations = 2 +ij_java_keep_builder_methods_indents = false +ij_java_keep_control_statement_in_one_line = true +ij_java_keep_first_column_comment = true +ij_java_keep_indents_on_empty_lines = false +ij_java_keep_line_breaks = true +ij_java_keep_multiple_expressions_in_one_line = false +ij_java_keep_simple_blocks_in_one_line = false +ij_java_keep_simple_classes_in_one_line = false +ij_java_keep_simple_lambdas_in_one_line = false +ij_java_keep_simple_methods_in_one_line = false +ij_java_label_indent_absolute = false +ij_java_label_indent_size = 0 +ij_java_lambda_brace_style = end_of_line +ij_java_layout_static_imports_separately = true +ij_java_line_comment_add_space = false +ij_java_line_comment_at_first_column = true +ij_java_method_annotation_wrap = split_into_lines +ij_java_method_brace_style = end_of_line +ij_java_method_call_chain_wrap = off +ij_java_method_parameters_new_line_after_left_paren = false +ij_java_method_parameters_right_paren_on_new_line = false +ij_java_method_parameters_wrap = off +ij_java_modifier_list_wrap = false +ij_java_names_count_to_use_import_on_demand = 99 +ij_java_new_line_after_lparen_in_record_header = false +ij_java_parameter_annotation_wrap = off +ij_java_parentheses_expression_new_line_after_left_paren = false +ij_java_parentheses_expression_right_paren_on_new_line = false +ij_java_place_assignment_sign_on_next_line = false +ij_java_prefer_longer_names = true +ij_java_prefer_parameters_wrap = false +ij_java_record_components_wrap = normal +ij_java_repeat_synchronized = true +ij_java_replace_instanceof_and_cast = false +ij_java_replace_null_check = true +ij_java_replace_sum_lambda_with_method_ref = true +ij_java_resource_list_new_line_after_left_paren = false +ij_java_resource_list_right_paren_on_new_line = false +ij_java_resource_list_wrap = off +ij_java_rparen_on_new_line_in_record_header = false +ij_java_space_after_closing_angle_bracket_in_type_argument = false +ij_java_space_after_colon = true +ij_java_space_after_comma = true +ij_java_space_after_comma_in_type_arguments = true +ij_java_space_after_for_semicolon = true +ij_java_space_after_quest = true +ij_java_space_after_type_cast = true +ij_java_space_before_annotation_array_initializer_left_brace = false +ij_java_space_before_annotation_parameter_list = false +ij_java_space_before_array_initializer_left_brace = false +ij_java_space_before_catch_keyword = true +ij_java_space_before_catch_left_brace = true +ij_java_space_before_catch_parentheses = true +ij_java_space_before_class_left_brace = true +ij_java_space_before_colon = true +ij_java_space_before_colon_in_foreach = true +ij_java_space_before_comma = false +ij_java_space_before_do_left_brace = true +ij_java_space_before_else_keyword = true +ij_java_space_before_else_left_brace = true +ij_java_space_before_finally_keyword = true +ij_java_space_before_finally_left_brace = true +ij_java_space_before_for_left_brace = true +ij_java_space_before_for_parentheses = true +ij_java_space_before_for_semicolon = false +ij_java_space_before_if_left_brace = true +ij_java_space_before_if_parentheses = true +ij_java_space_before_method_call_parentheses = false +ij_java_space_before_method_left_brace = true +ij_java_space_before_method_parentheses = false +ij_java_space_before_opening_angle_bracket_in_type_parameter = false +ij_java_space_before_quest = true +ij_java_space_before_switch_left_brace = true +ij_java_space_before_switch_parentheses = true +ij_java_space_before_synchronized_left_brace = true +ij_java_space_before_synchronized_parentheses = true +ij_java_space_before_try_left_brace = true +ij_java_space_before_try_parentheses = true +ij_java_space_before_type_parameter_list = false +ij_java_space_before_while_keyword = true +ij_java_space_before_while_left_brace = true +ij_java_space_before_while_parentheses = true +ij_java_space_inside_one_line_enum_braces = false +ij_java_space_within_empty_array_initializer_braces = false +ij_java_space_within_empty_method_call_parentheses = false +ij_java_space_within_empty_method_parentheses = false +ij_java_spaces_around_additive_operators = true +ij_java_spaces_around_assignment_operators = true +ij_java_spaces_around_bitwise_operators = true +ij_java_spaces_around_equality_operators = true +ij_java_spaces_around_lambda_arrow = true +ij_java_spaces_around_logical_operators = true +ij_java_spaces_around_method_ref_dbl_colon = false +ij_java_spaces_around_multiplicative_operators = true +ij_java_spaces_around_relational_operators = true +ij_java_spaces_around_shift_operators = true +ij_java_spaces_around_type_bounds_in_type_parameters = true +ij_java_spaces_around_unary_operator = false +ij_java_spaces_within_angle_brackets = false +ij_java_spaces_within_annotation_parentheses = false +ij_java_spaces_within_array_initializer_braces = false +ij_java_spaces_within_braces = false +ij_java_spaces_within_brackets = false +ij_java_spaces_within_cast_parentheses = false +ij_java_spaces_within_catch_parentheses = false +ij_java_spaces_within_for_parentheses = false +ij_java_spaces_within_if_parentheses = false +ij_java_spaces_within_method_call_parentheses = false +ij_java_spaces_within_method_parentheses = false +ij_java_spaces_within_parentheses = false +ij_java_spaces_within_record_header = false +ij_java_spaces_within_switch_parentheses = false +ij_java_spaces_within_synchronized_parentheses = false +ij_java_spaces_within_try_parentheses = false +ij_java_spaces_within_while_parentheses = false +ij_java_special_else_if_treatment = true +ij_java_subclass_name_suffix = Impl +ij_java_ternary_operation_signs_on_next_line = false +ij_java_ternary_operation_wrap = off +ij_java_test_name_suffix = Test +ij_java_throws_keyword_wrap = off +ij_java_throws_list_wrap = off +ij_java_use_external_annotations = false +ij_java_use_fq_class_names = false +ij_java_use_relative_indents = false +ij_java_use_single_class_imports = true +ij_java_variable_annotation_wrap = off +ij_java_visibility = public +ij_java_while_brace_force = never +ij_java_while_on_new_line = false +ij_java_wrap_comments = false +ij_java_wrap_first_method_in_call_chain = false +ij_java_wrap_long_lines = false + +[*.properties] +ij_properties_align_group_field_declarations = false +ij_properties_keep_blank_lines = false +ij_properties_key_value_delimiter = equals +ij_properties_spaces_around_key_value_delimiter = false + +[.editorconfig] +ij_editorconfig_align_group_field_declarations = false +ij_editorconfig_space_after_colon = false +ij_editorconfig_space_after_comma = true +ij_editorconfig_space_before_colon = false +ij_editorconfig_space_before_comma = false +ij_editorconfig_spaces_around_assignment_operators = true + +[{*.ant,*.fxml,*.jhm,*.jnlp,*.jrxml,*.rng,*.tld,*.wsdl,*.xml,*.xsd,*.xsl,*.xslt,*.xul}] +ij_continuation_indent_size = 4 +ij_xml_align_attributes = false +ij_xml_align_text = false +ij_xml_attribute_wrap = normal +ij_xml_block_comment_at_first_column = true +ij_xml_keep_blank_lines = 2 +ij_xml_keep_indents_on_empty_lines = false +ij_xml_keep_line_breaks = false +ij_xml_keep_line_breaks_in_text = true +ij_xml_keep_whitespaces = false +ij_xml_keep_whitespaces_around_cdata = preserve +ij_xml_keep_whitespaces_inside_cdata = false +ij_xml_line_comment_at_first_column = true +ij_xml_space_after_tag_name = false +ij_xml_space_around_equals_in_attribute = false +ij_xml_space_inside_empty_tag = true +ij_xml_text_wrap = normal +ij_xml_use_custom_settings = true + +[{*.bash,*.sh,*.zsh}] +indent_size = 2 +tab_width = 2 +ij_shell_binary_ops_start_line = false +ij_shell_keep_column_alignment_padding = false +ij_shell_minify_program = false +ij_shell_redirect_followed_by_space = false +ij_shell_switch_cases_indented = false +ij_shell_use_unix_line_separator = true + +[{*.c,*.c++,*.cc,*.cp,*.cpp,*.cu,*.cuh,*.cxx,*.h,*.h++,*.hh,*.hp,*.hpp,*.hxx,*.i,*.icc,*.ii,*.inl,*.ino,*.ipp,*.m,*.mm,*.pch,*.tcc,*.tpp}] +ij_c_add_brief_tag = false +ij_c_add_getter_prefix = true +ij_c_add_setter_prefix = true +ij_c_align_dictionary_pair_values = false +ij_c_align_group_field_declarations = false +ij_c_align_init_list_in_columns = true +ij_c_align_multiline_array_initializer_expression = true +ij_c_align_multiline_assignment = true +ij_c_align_multiline_binary_operation = true +ij_c_align_multiline_chained_methods = false +ij_c_align_multiline_for = true +ij_c_align_multiline_ternary_operation = true +ij_c_array_initializer_comma_on_next_line = false +ij_c_array_initializer_new_line_after_left_brace = false +ij_c_array_initializer_right_brace_on_new_line = false +ij_c_array_initializer_wrap = normal +ij_c_assignment_wrap = off +ij_c_binary_operation_sign_on_next_line = false +ij_c_binary_operation_wrap = normal +ij_c_blank_lines_after_class_header = 0 +ij_c_blank_lines_after_imports = 1 +ij_c_blank_lines_around_class = 1 +ij_c_blank_lines_around_field = 0 +ij_c_blank_lines_around_field_in_interface = 0 +ij_c_blank_lines_around_method = 1 +ij_c_blank_lines_around_method_in_interface = 1 +ij_c_blank_lines_around_namespace = 0 +ij_c_blank_lines_around_properties_in_declaration = 0 +ij_c_blank_lines_around_properties_in_interface = 0 +ij_c_blank_lines_before_imports = 1 +ij_c_blank_lines_before_method_body = 0 +ij_c_block_brace_placement = end_of_line +ij_c_block_brace_style = end_of_line +ij_c_block_comment_at_first_column = true +ij_c_catch_on_new_line = false +ij_c_class_brace_style = end_of_line +ij_c_class_constructor_init_list_align_multiline = true +ij_c_class_constructor_init_list_comma_on_next_line = false +ij_c_class_constructor_init_list_new_line_after_colon = never +ij_c_class_constructor_init_list_new_line_before_colon = if_long +ij_c_class_constructor_init_list_wrap = normal +ij_c_copy_is_deep = false +ij_c_create_interface_for_categories = true +ij_c_declare_generated_methods = true +ij_c_description_include_member_names = true +ij_c_discharged_short_ternary_operator = false +ij_c_do_not_add_breaks = false +ij_c_do_while_brace_force = never +ij_c_else_on_new_line = false +ij_c_enum_constants_comma_on_next_line = false +ij_c_enum_constants_wrap = on_every_item +ij_c_for_brace_force = never +ij_c_for_statement_new_line_after_left_paren = false +ij_c_for_statement_right_paren_on_new_line = false +ij_c_for_statement_wrap = off +ij_c_function_brace_placement = end_of_line +ij_c_function_call_arguments_align_multiline = true +ij_c_function_call_arguments_align_multiline_pars = false +ij_c_function_call_arguments_comma_on_next_line = false +ij_c_function_call_arguments_new_line_after_lpar = false +ij_c_function_call_arguments_new_line_before_rpar = false +ij_c_function_call_arguments_wrap = normal +ij_c_function_non_top_after_return_type_wrap = normal +ij_c_function_parameters_align_multiline = true +ij_c_function_parameters_align_multiline_pars = false +ij_c_function_parameters_comma_on_next_line = false +ij_c_function_parameters_new_line_after_lpar = false +ij_c_function_parameters_new_line_before_rpar = false +ij_c_function_parameters_wrap = normal +ij_c_function_top_after_return_type_wrap = normal +ij_c_generate_additional_eq_operators = true +ij_c_generate_additional_rel_operators = true +ij_c_generate_class_constructor = true +ij_c_generate_comparison_operators_use_std_tie = false +ij_c_generate_instance_variables_for_properties = ask +ij_c_generate_operators_as_members = true +ij_c_header_guard_style_pattern = ${PROJECT_NAME}_${FILE_NAME}_${EXT} +ij_c_if_brace_force = never +ij_c_in_line_short_ternary_operator = true +ij_c_indent_block_comment = true +ij_c_indent_c_struct_members = 4 +ij_c_indent_case_from_switch = true +ij_c_indent_class_members = 4 +ij_c_indent_directive_as_code = false +ij_c_indent_implementation_members = 0 +ij_c_indent_inside_code_block = 4 +ij_c_indent_interface_members = 0 +ij_c_indent_interface_members_except_ivars_block = false +ij_c_indent_namespace_members = 4 +ij_c_indent_preprocessor_directive = 0 +ij_c_indent_visibility_keywords = 0 +ij_c_insert_override = true +ij_c_insert_virtual_with_override = false +ij_c_introduce_auto_vars = false +ij_c_introduce_const_params = false +ij_c_introduce_const_vars = false +ij_c_introduce_generate_property = false +ij_c_introduce_generate_synthesize = true +ij_c_introduce_globals_to_header = true +ij_c_introduce_prop_to_private_category = false +ij_c_introduce_static_consts = true +ij_c_introduce_use_ns_types = false +ij_c_ivars_prefix = _ +ij_c_keep_blank_lines_before_end = 2 +ij_c_keep_blank_lines_before_right_brace = 2 +ij_c_keep_blank_lines_in_code = 2 +ij_c_keep_blank_lines_in_declarations = 2 +ij_c_keep_case_expressions_in_one_line = false +ij_c_keep_control_statement_in_one_line = true +ij_c_keep_directive_at_first_column = true +ij_c_keep_first_column_comment = true +ij_c_keep_line_breaks = true +ij_c_keep_nested_namespaces_in_one_line = false +ij_c_keep_simple_blocks_in_one_line = true +ij_c_keep_simple_methods_in_one_line = true +ij_c_keep_structures_in_one_line = false +ij_c_lambda_capture_list_align_multiline = false +ij_c_lambda_capture_list_align_multiline_bracket = false +ij_c_lambda_capture_list_comma_on_next_line = false +ij_c_lambda_capture_list_new_line_after_lbracket = false +ij_c_lambda_capture_list_new_line_before_rbracket = false +ij_c_lambda_capture_list_wrap = off +ij_c_line_comment_add_space = false +ij_c_line_comment_at_first_column = true +ij_c_method_brace_placement = end_of_line +ij_c_method_call_arguments_align_by_colons = true +ij_c_method_call_arguments_align_multiline = false +ij_c_method_call_arguments_special_dictionary_pairs_treatment = true +ij_c_method_call_arguments_wrap = off +ij_c_method_call_chain_wrap = off +ij_c_method_parameters_align_by_colons = true +ij_c_method_parameters_align_multiline = false +ij_c_method_parameters_wrap = off +ij_c_namespace_brace_placement = end_of_line +ij_c_parentheses_expression_new_line_after_left_paren = false +ij_c_parentheses_expression_right_paren_on_new_line = false +ij_c_place_assignment_sign_on_next_line = false +ij_c_property_nonatomic = true +ij_c_put_ivars_to_implementation = true +ij_c_refactor_compatibility_aliases_and_classes = true +ij_c_refactor_properties_and_ivars = true +ij_c_release_style = ivar +ij_c_retain_object_parameters_in_constructor = true +ij_c_semicolon_after_method_signature = false +ij_c_shift_operation_align_multiline = true +ij_c_shift_operation_wrap = normal +ij_c_show_non_virtual_functions = false +ij_c_space_after_colon = true +ij_c_space_after_colon_in_selector = false +ij_c_space_after_comma = true +ij_c_space_after_cup_in_blocks = false +ij_c_space_after_dictionary_literal_colon = true +ij_c_space_after_for_semicolon = true +ij_c_space_after_init_list_colon = true +ij_c_space_after_method_parameter_type_parentheses = false +ij_c_space_after_method_return_type_parentheses = false +ij_c_space_after_pointer_in_declaration = false +ij_c_space_after_quest = true +ij_c_space_after_reference_in_declaration = false +ij_c_space_after_reference_in_rvalue = false +ij_c_space_after_structures_rbrace = true +ij_c_space_after_superclass_colon = true +ij_c_space_after_type_cast = true +ij_c_space_after_visibility_sign_in_method_declaration = true +ij_c_space_before_autorelease_pool_lbrace = true +ij_c_space_before_catch_keyword = true +ij_c_space_before_catch_left_brace = true +ij_c_space_before_catch_parentheses = true +ij_c_space_before_category_parentheses = true +ij_c_space_before_chained_send_message = true +ij_c_space_before_class_left_brace = true +ij_c_space_before_colon = true +ij_c_space_before_comma = false +ij_c_space_before_dictionary_literal_colon = false +ij_c_space_before_do_left_brace = true +ij_c_space_before_else_keyword = true +ij_c_space_before_else_left_brace = true +ij_c_space_before_for_left_brace = true +ij_c_space_before_for_parentheses = true +ij_c_space_before_for_semicolon = false +ij_c_space_before_if_left_brace = true +ij_c_space_before_if_parentheses = true +ij_c_space_before_init_list = false +ij_c_space_before_init_list_colon = true +ij_c_space_before_method_call_parentheses = false +ij_c_space_before_method_left_brace = true +ij_c_space_before_method_parentheses = false +ij_c_space_before_namespace_lbrace = true +ij_c_space_before_pointer_in_declaration = true +ij_c_space_before_property_attributes_parentheses = false +ij_c_space_before_protocols_brackets = true +ij_c_space_before_quest = true +ij_c_space_before_reference_in_declaration = true +ij_c_space_before_superclass_colon = true +ij_c_space_before_switch_left_brace = true +ij_c_space_before_switch_parentheses = true +ij_c_space_before_template_call_lt = false +ij_c_space_before_template_declaration_lt = false +ij_c_space_before_try_left_brace = true +ij_c_space_before_while_keyword = true +ij_c_space_before_while_left_brace = true +ij_c_space_before_while_parentheses = true +ij_c_space_between_adjacent_brackets = false +ij_c_space_between_operator_and_punctuator = false +ij_c_space_within_empty_array_initializer_braces = false +ij_c_spaces_around_additive_operators = true +ij_c_spaces_around_assignment_operators = true +ij_c_spaces_around_bitwise_operators = true +ij_c_spaces_around_equality_operators = true +ij_c_spaces_around_lambda_arrow = true +ij_c_spaces_around_logical_operators = true +ij_c_spaces_around_multiplicative_operators = true +ij_c_spaces_around_pm_operators = false +ij_c_spaces_around_relational_operators = true +ij_c_spaces_around_shift_operators = true +ij_c_spaces_around_unary_operator = false +ij_c_spaces_within_array_initializer_braces = false +ij_c_spaces_within_braces = true +ij_c_spaces_within_brackets = false +ij_c_spaces_within_cast_parentheses = false +ij_c_spaces_within_catch_parentheses = false +ij_c_spaces_within_category_parentheses = false +ij_c_spaces_within_empty_braces = false +ij_c_spaces_within_empty_function_call_parentheses = false +ij_c_spaces_within_empty_function_declaration_parentheses = false +ij_c_spaces_within_empty_lambda_capture_list_bracket = false +ij_c_spaces_within_empty_template_call_ltgt = false +ij_c_spaces_within_empty_template_declaration_ltgt = false +ij_c_spaces_within_for_parentheses = false +ij_c_spaces_within_function_call_parentheses = false +ij_c_spaces_within_function_declaration_parentheses = false +ij_c_spaces_within_if_parentheses = false +ij_c_spaces_within_lambda_capture_list_bracket = false +ij_c_spaces_within_method_parameter_type_parentheses = false +ij_c_spaces_within_method_return_type_parentheses = false +ij_c_spaces_within_parentheses = false +ij_c_spaces_within_property_attributes_parentheses = false +ij_c_spaces_within_protocols_brackets = false +ij_c_spaces_within_send_message_brackets = false +ij_c_spaces_within_switch_parentheses = false +ij_c_spaces_within_template_call_ltgt = false +ij_c_spaces_within_template_declaration_ltgt = false +ij_c_spaces_within_template_double_gt = true +ij_c_spaces_within_while_parentheses = false +ij_c_special_else_if_treatment = true +ij_c_superclass_list_after_colon = never +ij_c_superclass_list_align_multiline = true +ij_c_superclass_list_before_colon = if_long +ij_c_superclass_list_comma_on_next_line = false +ij_c_superclass_list_wrap = on_every_item +ij_c_tag_prefix_of_block_comment = at +ij_c_tag_prefix_of_line_comment = back_slash +ij_c_template_call_arguments_align_multiline = false +ij_c_template_call_arguments_align_multiline_pars = false +ij_c_template_call_arguments_comma_on_next_line = false +ij_c_template_call_arguments_new_line_after_lt = false +ij_c_template_call_arguments_new_line_before_gt = false +ij_c_template_call_arguments_wrap = off +ij_c_template_declaration_function_body_indent = false +ij_c_template_declaration_function_wrap = split_into_lines +ij_c_template_declaration_struct_body_indent = false +ij_c_template_declaration_struct_wrap = split_into_lines +ij_c_template_parameters_align_multiline = false +ij_c_template_parameters_align_multiline_pars = false +ij_c_template_parameters_comma_on_next_line = false +ij_c_template_parameters_new_line_after_lt = false +ij_c_template_parameters_new_line_before_gt = false +ij_c_template_parameters_wrap = off +ij_c_ternary_operation_signs_on_next_line = true +ij_c_ternary_operation_wrap = normal +ij_c_type_qualifiers_placement = before +ij_c_use_modern_casts = true +ij_c_use_setters_in_constructor = true +ij_c_while_brace_force = never +ij_c_while_on_new_line = false +ij_c_wrap_property_declaration = off + +[{*.cmake,CMakeLists.txt}] +ij_cmake_align_multiline_parameters_in_calls = false +ij_cmake_force_commands_case = 2 +ij_cmake_keep_blank_lines_in_code = 2 +ij_cmake_space_before_for_parentheses = true +ij_cmake_space_before_if_parentheses = true +ij_cmake_space_before_method_call_parentheses = false +ij_cmake_space_before_method_parentheses = false +ij_cmake_space_before_while_parentheses = true +ij_cmake_spaces_within_for_parentheses = false +ij_cmake_spaces_within_if_parentheses = false +ij_cmake_spaces_within_method_call_parentheses = false +ij_cmake_spaces_within_method_parentheses = false +ij_cmake_spaces_within_while_parentheses = false + +[{*.gant,*.gradle,*.groovy,*.gy}] +ij_groovy_align_group_field_declarations = false +ij_groovy_align_multiline_array_initializer_expression = false +ij_groovy_align_multiline_assignment = false +ij_groovy_align_multiline_binary_operation = false +ij_groovy_align_multiline_chained_methods = false +ij_groovy_align_multiline_extends_list = false +ij_groovy_align_multiline_for = true +ij_groovy_align_multiline_list_or_map = true +ij_groovy_align_multiline_method_parentheses = false +ij_groovy_align_multiline_parameters = true +ij_groovy_align_multiline_parameters_in_calls = false +ij_groovy_align_multiline_resources = true +ij_groovy_align_multiline_ternary_operation = false +ij_groovy_align_multiline_throws_list = false +ij_groovy_align_named_args_in_map = true +ij_groovy_align_throws_keyword = false +ij_groovy_array_initializer_new_line_after_left_brace = false +ij_groovy_array_initializer_right_brace_on_new_line = false +ij_groovy_array_initializer_wrap = off +ij_groovy_assert_statement_wrap = off +ij_groovy_assignment_wrap = off +ij_groovy_binary_operation_wrap = off +ij_groovy_blank_lines_after_class_header = 0 +ij_groovy_blank_lines_after_imports = 1 +ij_groovy_blank_lines_after_package = 1 +ij_groovy_blank_lines_around_class = 1 +ij_groovy_blank_lines_around_field = 0 +ij_groovy_blank_lines_around_field_in_interface = 0 +ij_groovy_blank_lines_around_method = 1 +ij_groovy_blank_lines_around_method_in_interface = 1 +ij_groovy_blank_lines_before_imports = 1 +ij_groovy_blank_lines_before_method_body = 0 +ij_groovy_blank_lines_before_package = 0 +ij_groovy_block_brace_style = end_of_line +ij_groovy_block_comment_at_first_column = true +ij_groovy_call_parameters_new_line_after_left_paren = false +ij_groovy_call_parameters_right_paren_on_new_line = false +ij_groovy_call_parameters_wrap = off +ij_groovy_catch_on_new_line = false +ij_groovy_class_annotation_wrap = split_into_lines +ij_groovy_class_brace_style = end_of_line +ij_groovy_class_count_to_use_import_on_demand = 5 +ij_groovy_do_while_brace_force = never +ij_groovy_else_on_new_line = false +ij_groovy_enum_constants_wrap = off +ij_groovy_extends_keyword_wrap = off +ij_groovy_extends_list_wrap = off +ij_groovy_field_annotation_wrap = split_into_lines +ij_groovy_finally_on_new_line = false +ij_groovy_for_brace_force = never +ij_groovy_for_statement_new_line_after_left_paren = false +ij_groovy_for_statement_right_paren_on_new_line = false +ij_groovy_for_statement_wrap = off +ij_groovy_if_brace_force = never +ij_groovy_import_annotation_wrap = 2 +ij_groovy_imports_layout = *,|,javax.**,java.**,|,$* +ij_groovy_indent_case_from_switch = true +ij_groovy_indent_label_blocks = true +ij_groovy_insert_inner_class_imports = false +ij_groovy_keep_blank_lines_before_right_brace = 2 +ij_groovy_keep_blank_lines_in_code = 2 +ij_groovy_keep_blank_lines_in_declarations = 2 +ij_groovy_keep_control_statement_in_one_line = true +ij_groovy_keep_first_column_comment = true +ij_groovy_keep_indents_on_empty_lines = false +ij_groovy_keep_line_breaks = true +ij_groovy_keep_multiple_expressions_in_one_line = false +ij_groovy_keep_simple_blocks_in_one_line = false +ij_groovy_keep_simple_classes_in_one_line = true +ij_groovy_keep_simple_lambdas_in_one_line = true +ij_groovy_keep_simple_methods_in_one_line = true +ij_groovy_label_indent_absolute = false +ij_groovy_label_indent_size = 0 +ij_groovy_lambda_brace_style = end_of_line +ij_groovy_layout_static_imports_separately = true +ij_groovy_line_comment_add_space = false +ij_groovy_line_comment_at_first_column = true +ij_groovy_method_annotation_wrap = split_into_lines +ij_groovy_method_brace_style = end_of_line +ij_groovy_method_call_chain_wrap = off +ij_groovy_method_parameters_new_line_after_left_paren = false +ij_groovy_method_parameters_right_paren_on_new_line = false +ij_groovy_method_parameters_wrap = off +ij_groovy_modifier_list_wrap = false +ij_groovy_names_count_to_use_import_on_demand = 3 +ij_groovy_parameter_annotation_wrap = off +ij_groovy_parentheses_expression_new_line_after_left_paren = false +ij_groovy_parentheses_expression_right_paren_on_new_line = false +ij_groovy_prefer_parameters_wrap = false +ij_groovy_resource_list_new_line_after_left_paren = false +ij_groovy_resource_list_right_paren_on_new_line = false +ij_groovy_resource_list_wrap = off +ij_groovy_space_after_assert_separator = true +ij_groovy_space_after_colon = true +ij_groovy_space_after_comma = true +ij_groovy_space_after_comma_in_type_arguments = true +ij_groovy_space_after_for_semicolon = true +ij_groovy_space_after_quest = true +ij_groovy_space_after_type_cast = true +ij_groovy_space_before_annotation_parameter_list = false +ij_groovy_space_before_array_initializer_left_brace = false +ij_groovy_space_before_assert_separator = false +ij_groovy_space_before_catch_keyword = true +ij_groovy_space_before_catch_left_brace = true +ij_groovy_space_before_catch_parentheses = true +ij_groovy_space_before_class_left_brace = true +ij_groovy_space_before_closure_left_brace = true +ij_groovy_space_before_colon = true +ij_groovy_space_before_comma = false +ij_groovy_space_before_do_left_brace = true +ij_groovy_space_before_else_keyword = true +ij_groovy_space_before_else_left_brace = true +ij_groovy_space_before_finally_keyword = true +ij_groovy_space_before_finally_left_brace = true +ij_groovy_space_before_for_left_brace = true +ij_groovy_space_before_for_parentheses = true +ij_groovy_space_before_for_semicolon = false +ij_groovy_space_before_if_left_brace = true +ij_groovy_space_before_if_parentheses = true +ij_groovy_space_before_method_call_parentheses = false +ij_groovy_space_before_method_left_brace = true +ij_groovy_space_before_method_parentheses = false +ij_groovy_space_before_quest = true +ij_groovy_space_before_switch_left_brace = true +ij_groovy_space_before_switch_parentheses = true +ij_groovy_space_before_synchronized_left_brace = true +ij_groovy_space_before_synchronized_parentheses = true +ij_groovy_space_before_try_left_brace = true +ij_groovy_space_before_try_parentheses = true +ij_groovy_space_before_while_keyword = true +ij_groovy_space_before_while_left_brace = true +ij_groovy_space_before_while_parentheses = true +ij_groovy_space_in_named_argument = true +ij_groovy_space_in_named_argument_before_colon = false +ij_groovy_space_within_empty_array_initializer_braces = false +ij_groovy_space_within_empty_method_call_parentheses = false +ij_groovy_spaces_around_additive_operators = true +ij_groovy_spaces_around_assignment_operators = true +ij_groovy_spaces_around_bitwise_operators = true +ij_groovy_spaces_around_equality_operators = true +ij_groovy_spaces_around_lambda_arrow = true +ij_groovy_spaces_around_logical_operators = true +ij_groovy_spaces_around_multiplicative_operators = true +ij_groovy_spaces_around_regex_operators = true +ij_groovy_spaces_around_relational_operators = true +ij_groovy_spaces_around_shift_operators = true +ij_groovy_spaces_within_annotation_parentheses = false +ij_groovy_spaces_within_array_initializer_braces = false +ij_groovy_spaces_within_braces = true +ij_groovy_spaces_within_brackets = false +ij_groovy_spaces_within_cast_parentheses = false +ij_groovy_spaces_within_catch_parentheses = false +ij_groovy_spaces_within_for_parentheses = false +ij_groovy_spaces_within_gstring_injection_braces = false +ij_groovy_spaces_within_if_parentheses = false +ij_groovy_spaces_within_list_or_map = false +ij_groovy_spaces_within_method_call_parentheses = false +ij_groovy_spaces_within_method_parentheses = false +ij_groovy_spaces_within_parentheses = false +ij_groovy_spaces_within_switch_parentheses = false +ij_groovy_spaces_within_synchronized_parentheses = false +ij_groovy_spaces_within_try_parentheses = false +ij_groovy_spaces_within_tuple_expression = false +ij_groovy_spaces_within_while_parentheses = false +ij_groovy_special_else_if_treatment = true +ij_groovy_ternary_operation_wrap = off +ij_groovy_throws_keyword_wrap = off +ij_groovy_throws_list_wrap = off +ij_groovy_use_flying_geese_braces = false +ij_groovy_use_fq_class_names = false +ij_groovy_use_fq_class_names_in_javadoc = true +ij_groovy_use_relative_indents = false +ij_groovy_use_single_class_imports = true +ij_groovy_variable_annotation_wrap = off +ij_groovy_while_brace_force = never +ij_groovy_while_on_new_line = false +ij_groovy_wrap_long_lines = false + +[{*.gradle.kts,*.kt,*.kts,*.main.kts}] +ij_kotlin_align_in_columns_case_branch = true +ij_kotlin_align_multiline_binary_operation = false +ij_kotlin_align_multiline_extends_list = false +ij_kotlin_align_multiline_method_parentheses = false +ij_kotlin_align_multiline_parameters = true +ij_kotlin_align_multiline_parameters_in_calls = false +ij_kotlin_allow_trailing_comma = false +ij_kotlin_allow_trailing_comma_on_call_site = false +ij_kotlin_assignment_wrap = off +ij_kotlin_blank_lines_after_class_header = 0 +ij_kotlin_blank_lines_around_block_when_branches = 0 +ij_kotlin_blank_lines_before_declaration_with_comment_or_annotation_on_separate_line = 1 +ij_kotlin_block_comment_at_first_column = true +ij_kotlin_call_parameters_new_line_after_left_paren = false +ij_kotlin_call_parameters_right_paren_on_new_line = false +ij_kotlin_call_parameters_wrap = off +ij_kotlin_catch_on_new_line = false +ij_kotlin_class_annotation_wrap = off +ij_kotlin_code_style_defaults = KOTLIN_OFFICIAL +ij_kotlin_continuation_indent_for_chained_calls = true +ij_kotlin_continuation_indent_for_expression_bodies = true +ij_kotlin_continuation_indent_in_argument_lists = true +ij_kotlin_continuation_indent_in_elvis = true +ij_kotlin_continuation_indent_in_if_conditions = true +ij_kotlin_continuation_indent_in_parameter_lists = true +ij_kotlin_continuation_indent_in_supertype_lists = true +ij_kotlin_else_on_new_line = false +ij_kotlin_enum_constants_wrap = off +ij_kotlin_extends_list_wrap = off +ij_kotlin_field_annotation_wrap = normal +ij_kotlin_finally_on_new_line = false +ij_kotlin_if_rparen_on_new_line = false +ij_kotlin_import_nested_classes = false +ij_kotlin_imports_layout = *,java.**,javax.**,kotlin.**,^ +ij_kotlin_insert_whitespaces_in_simple_one_line_method = true +ij_kotlin_keep_blank_lines_before_right_brace = 0 +ij_kotlin_keep_blank_lines_in_code = 1 +ij_kotlin_keep_blank_lines_in_declarations = 1 +ij_kotlin_keep_first_column_comment = true +ij_kotlin_keep_indents_on_empty_lines = false +ij_kotlin_keep_line_breaks = true +ij_kotlin_lbrace_on_next_line = false +ij_kotlin_line_comment_add_space = false +ij_kotlin_line_comment_at_first_column = true +ij_kotlin_method_annotation_wrap = split_into_lines +ij_kotlin_method_call_chain_wrap = off +ij_kotlin_method_parameters_new_line_after_left_paren = false +ij_kotlin_method_parameters_right_paren_on_new_line = false +ij_kotlin_method_parameters_wrap = off +ij_kotlin_name_count_to_use_star_import = 2147483647 +ij_kotlin_name_count_to_use_star_import_for_members = 2147483647 +ij_kotlin_packages_to_use_import_on_demand = kotlinx.android.synthetic.** +ij_kotlin_parameter_annotation_wrap = off +ij_kotlin_space_after_comma = true +ij_kotlin_space_after_extend_colon = true +ij_kotlin_space_after_type_colon = true +ij_kotlin_space_before_catch_parentheses = true +ij_kotlin_space_before_comma = false +ij_kotlin_space_before_extend_colon = true +ij_kotlin_space_before_for_parentheses = true +ij_kotlin_space_before_if_parentheses = true +ij_kotlin_space_before_lambda_arrow = true +ij_kotlin_space_before_type_colon = false +ij_kotlin_space_before_when_parentheses = true +ij_kotlin_space_before_while_parentheses = true +ij_kotlin_spaces_around_additive_operators = true +ij_kotlin_spaces_around_assignment_operators = true +ij_kotlin_spaces_around_equality_operators = true +ij_kotlin_spaces_around_function_type_arrow = true +ij_kotlin_spaces_around_logical_operators = true +ij_kotlin_spaces_around_multiplicative_operators = true +ij_kotlin_spaces_around_range = false +ij_kotlin_spaces_around_relational_operators = true +ij_kotlin_spaces_around_unary_operator = false +ij_kotlin_spaces_around_when_arrow = true +ij_kotlin_use_custom_formatting_for_modifiers = true +ij_kotlin_variable_annotation_wrap = off +ij_kotlin_while_on_new_line = false +ij_kotlin_wrap_elvis_expressions = 1 +ij_kotlin_wrap_expression_body_functions = 0 +ij_kotlin_wrap_first_method_in_call_chain = false + +[{*.har,*.json}] +indent_size = 2 +ij_json_keep_blank_lines_in_code = 0 +ij_json_keep_indents_on_empty_lines = false +ij_json_keep_line_breaks = true +ij_json_space_after_colon = true +ij_json_space_after_comma = true +ij_json_space_before_colon = true +ij_json_space_before_comma = false +ij_json_spaces_within_braces = false +ij_json_spaces_within_brackets = false +ij_json_wrap_long_lines = false + +[{*.htm,*.html,*.sht,*.shtm,*.shtml}] +ij_html_add_new_line_before_tags = body,div,p,form,h1,h2,h3 +ij_html_align_attributes = true +ij_html_align_text = false +ij_html_attribute_wrap = normal +ij_html_block_comment_at_first_column = true +ij_html_do_not_align_children_of_min_lines = 0 +ij_html_do_not_break_if_inline_tags = title,h1,h2,h3,h4,h5,h6,p +ij_html_do_not_indent_children_of_tags = html,body,thead,tbody,tfoot +ij_html_enforce_quotes = false +ij_html_inline_tags = a,abbr,acronym,b,basefont,bdo,big,br,cite,cite,code,dfn,em,font,i,img,input,kbd,label,q,s,samp,select,small,span,strike,strong,sub,sup,textarea,tt,u,var +ij_html_keep_blank_lines = 2 +ij_html_keep_indents_on_empty_lines = false +ij_html_keep_line_breaks = true +ij_html_keep_line_breaks_in_text = true +ij_html_keep_whitespaces = false +ij_html_keep_whitespaces_inside = span,pre,textarea +ij_html_line_comment_at_first_column = true +ij_html_new_line_after_last_attribute = never +ij_html_new_line_before_first_attribute = never +ij_html_quote_style = double +ij_html_remove_new_line_before_tags = br +ij_html_space_after_tag_name = false +ij_html_space_around_equality_in_attribute = false +ij_html_space_inside_empty_tag = false +ij_html_text_wrap = normal +ij_html_uniform_ident = false + +[{*.yaml,*.yml}] +indent_size = 2 +ij_yaml_align_values_properties = do_not_align +ij_yaml_autoinsert_sequence_marker = true +ij_yaml_block_mapping_on_new_line = false +ij_yaml_indent_sequence_value = true +ij_yaml_keep_indents_on_empty_lines = false +ij_yaml_keep_line_breaks = true +ij_yaml_sequence_on_new_line = false +ij_yaml_space_before_colon = false +ij_yaml_spaces_within_braces = true +ij_yaml_spaces_within_brackets = true diff --git a/.github/ISSUE_TEMPLATE/release.yml b/.github/ISSUE_TEMPLATE/release.yml index 582998d492..c22b00e2e9 100644 --- a/.github/ISSUE_TEMPLATE/release.yml +++ b/.github/ISSUE_TEMPLATE/release.yml @@ -18,6 +18,7 @@ body: - [ ] Weblate sync, fix lint issue if any (in a dedicated PR) - [ ] Check the update of the store descriptions (using Google Translate if necessary) to ensure that the changes are acceptable to be published to the stores. + - [ ] While Weblate is locked, and after the PR from Weblate has been merged, handle all the TODOs in the main `strings.xml` file - [ ] Run the script `./tools/release/pushPlayStoreMetaData.sh`. You can check in the GooglePlay console the Activity log to check the effect. ### Do the release diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md index 7096e6bdb5..3940f2ca9e 100644 --- a/.github/PULL_REQUEST_TEMPLATE.md +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -17,7 +17,17 @@ ## Screenshots / GIFs - + + + ## Tests diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 4ff935fad1..22b3a1727d 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -26,7 +26,7 @@ jobs: cancel-in-progress: true steps: - uses: actions/checkout@v3 - - uses: actions/cache@v2 + - uses: actions/cache@v3 with: path: | ~/.gradle/caches @@ -37,7 +37,7 @@ jobs: - name: Assemble ${{ matrix.target }} debug apk run: ./gradlew assemble${{ matrix.target }}Debug $CI_GRADLE_ARG_PROPERTIES --stacktrace - name: Upload ${{ matrix.target }} debug APKs - uses: actions/upload-artifact@v2 + uses: actions/upload-artifact@v3 with: name: vector-${{ matrix.target }}-debug path: | @@ -50,7 +50,7 @@ jobs: # Only runs on main, no concurrency. steps: - uses: actions/checkout@v3 - - uses: actions/cache@v2 + - uses: actions/cache@v3 with: path: | ~/.gradle/caches @@ -61,7 +61,7 @@ jobs: - name: Assemble GPlay unsigned apk run: ./gradlew clean assembleGplayRelease $CI_GRADLE_ARG_PROPERTIES --stacktrace - name: Upload Gplay unsigned APKs - uses: actions/upload-artifact@v2 + uses: actions/upload-artifact@v3 with: name: vector-gplay-release-unsigned path: | diff --git a/.github/workflows/nightly.yml b/.github/workflows/nightly.yml index d8c1bb6c49..502e3e275f 100644 --- a/.github/workflows/nightly.yml +++ b/.github/workflows/nightly.yml @@ -26,7 +26,7 @@ jobs: steps: - uses: actions/checkout@v3 - uses: gradle/wrapper-validation-action@v1 - - uses: actions/setup-java@v2 + - uses: actions/setup-java@v3 with: distribution: 'adopt' java-version: 11 @@ -34,7 +34,7 @@ jobs: uses: actions/setup-python@v3 with: python-version: 3.8 - - uses: actions/cache@v2 + - uses: actions/cache@v3 with: path: | ~/.gradle/caches @@ -43,7 +43,7 @@ jobs: restore-keys: | ${{ runner.os }}-gradle- - name: Start synapse server - uses: michaelkaye/setup-matrix-synapse@v0.3.0 + uses: michaelkaye/setup-matrix-synapse@v1.0.1 with: uploadLogs: true httpPort: 8080 @@ -174,7 +174,7 @@ jobs: # package: class PermalinkParserTest - name: Find Comment if: always() && github.event_name == 'pull_request' - uses: peter-evans/find-comment@v1 + uses: peter-evans/find-comment@v2 id: fc with: issue-number: ${{ github.event.pull_request.number }} @@ -182,7 +182,7 @@ jobs: body-includes: Integration Tests Results - name: Publish results to PR if: always() && github.event_name == 'pull_request' - uses: peter-evans/create-or-update-comment@v1 + uses: peter-evans/create-or-update-comment@v2 with: comment-id: ${{ steps.fc.outputs.comment-id }} issue-number: ${{ github.event.pull_request.number }} @@ -196,7 +196,7 @@ jobs: - `[org.matrix.android.sdk.PermalinkParserTest]`
${{ steps.get-comment-body-permalink.outputs.permalink }} edit-mode: replace - name: Upload Test Report Log - uses: actions/upload-artifact@v2 + uses: actions/upload-artifact@v3 if: always() with: name: integrationtest-error-results @@ -221,7 +221,7 @@ jobs: uses: actions/setup-python@v3 with: python-version: 3.8 - - uses: actions/cache@v2 + - uses: actions/cache@v3 with: path: | ~/.gradle/caches @@ -230,12 +230,12 @@ jobs: restore-keys: | ${{ runner.os }}-gradle- - name: Start synapse server - uses: michaelkaye/setup-matrix-synapse@v0.3.0 + uses: michaelkaye/setup-matrix-synapse@v1.0.1 with: uploadLogs: true httpPort: 8080 disableRateLimiting: true - - uses: actions/setup-java@v2 + - uses: actions/setup-java@v3 with: distribution: 'adopt' java-version: '11' @@ -256,7 +256,7 @@ jobs: adb logcat >> emulator.log & ./gradlew $CI_GRADLE_ARG_PROPERTIES connectedGplayDebugAndroidTest -Pandroid.testInstrumentationRunnerArguments.class=im.vector.app.ui.UiAllScreensSanityTest || (adb pull storage/emulated/0/Pictures/failure_screenshots && exit 1 ) - name: Upload Test Report Log - uses: actions/upload-artifact@v2 + uses: actions/upload-artifact@v3 if: always() with: name: uitest-error-results @@ -265,14 +265,15 @@ jobs: failure_screenshots/ codecov-units: + name: Unit tests with code coverage runs-on: macos-latest steps: - uses: actions/checkout@v3 - - uses: actions/setup-java@v2 + - uses: actions/setup-java@v3 with: distribution: 'adopt' java-version: '11' - - uses: actions/cache@v2 + - uses: actions/cache@v3 with: path: | ~/.gradle/caches @@ -282,7 +283,7 @@ jobs: ${{ runner.os }}-gradle- - run: ./gradlew allCodeCoverageReport $CI_GRADLE_ARG_PROPERTIES - name: Upload Codecov data - uses: actions/upload-artifact@v2 + uses: actions/upload-artifact@v3 if: always() with: name: codecov-xml @@ -290,17 +291,18 @@ jobs: build/reports/jacoco/allCodeCoverageReport/allCodeCoverageReport.xml sonarqube: + name: Sonarqube upload runs-on: macos-latest - if: always() + if: always() && github.event_name == 'schedule' needs: - codecov-units steps: - uses: actions/checkout@v3 - - uses: actions/setup-java@v2 + - uses: actions/setup-java@v3 with: distribution: 'adopt' java-version: '11' - - uses: actions/cache@v2 + - uses: actions/cache@v3 with: path: | ~/.gradle/caches @@ -317,8 +319,9 @@ jobs: env: ORG_GRADLE_PROJECT_SONAR_LOGIN: ${{ secrets.SONAR_TOKEN }} -# Notify the channel about scheduled runs, do not notify for manually triggered runs +# Notify the channel about scheduled runs, or pushes to the release branches, do not notify for manually triggered runs notify: + name: Notify matrix runs-on: ubuntu-latest needs: - integration-tests @@ -327,10 +330,9 @@ jobs: if: always() && github.event_name != 'workflow_dispatch' # No concurrency required, runs every time on a schedule. steps: - - uses: michaelkaye/matrix-hookshot-action@v0.3.0 + - uses: michaelkaye/matrix-hookshot-action@v1.0.0 with: github_token: ${{ secrets.GITHUB_TOKEN }} - matrix_access_token: ${{ secrets.ELEMENT_ANDROID_NOTIFICATION_ACCESS_TOKEN }} - matrix_room_id: ${{ secrets.ELEMENT_ANDROID_INTERNAL_ROOM_ID }} - text_template: "Nightly test run: {{#each job_statuses }}{{#with this }}{{#if completed }} {{name}} {{conclusion}} at {{completed_at}}, {{/if}}{{/with}}{{/each}}" - html_template: "Nightly test run results: {{#each job_statuses }}{{#with this }}{{#if completed }}
{{name}} {{conclusion}} at {{completed_at}} [details]{{/if}}{{/with}}{{/each}}" + hookshot_url: ${{ secrets.ELEMENT_ANDROID_HOOKSHOT_URL }} + text_template: "{{#if '${{ github.event_name }}' == 'schedule' }}Nightly test run{{else}}Test run (on ${{ github.ref }}){{/if }}: {{#each job_statuses }}{{#with this }}{{#if completed }} {{name}} {{conclusion}} at {{completed_at}}, {{/if}}{{/with}}{{/each}}" + html_template: "{{#if '${{ github.event_name }}' == 'schedule' }}Nightly test run{{else}}Test run (on ${{ github.ref }}){{/if }}: {{#each job_statuses }}{{#with this }}{{#if completed }}
{{icon conclusion}} {{name}} {{conclusion}} at {{completed_at}} [details]{{/if}}{{/with}}{{/each}}" diff --git a/.github/workflows/quality.yml b/.github/workflows/quality.yml index a588b91449..dee596980f 100644 --- a/.github/workflows/quality.yml +++ b/.github/workflows/quality.yml @@ -29,7 +29,7 @@ jobs: ./gradlew ktlintCheck --continue - name: Upload reports if: always() - uses: actions/upload-artifact@v2 + uses: actions/upload-artifact@v3 with: name: ktlinting-report path: | @@ -59,7 +59,7 @@ jobs: fi - name: Find Comment if: always() && github.event_name == 'pull_request' - uses: peter-evans/find-comment@v1 + uses: peter-evans/find-comment@v2 id: fc with: issue-number: ${{ github.event.pull_request.number }} @@ -67,7 +67,7 @@ jobs: body-includes: Ktlint Results - name: Add comment if needed if: always() && github.event_name == 'pull_request' && steps.ktlint-results.outputs.add_comment == 'true' - uses: peter-evans/create-or-update-comment@v1 + uses: peter-evans/create-or-update-comment@v2 with: comment-id: ${{ steps.fc.outputs.comment-id }} issue-number: ${{ github.event.pull_request.number }} @@ -97,7 +97,7 @@ jobs: cancel-in-progress: true steps: - uses: actions/checkout@v3 - - uses: actions/cache@v2 + - uses: actions/cache@v3 with: path: | ~/.gradle/caches @@ -109,7 +109,7 @@ jobs: run: ./gradlew clean :vector:lint --stacktrace - name: Upload reports if: always() - uses: actions/upload-artifact@v2 + uses: actions/upload-artifact@v3 with: name: lint-report path: | @@ -130,7 +130,7 @@ jobs: cancel-in-progress: true steps: - uses: actions/checkout@v3 - - uses: actions/cache@v2 + - uses: actions/cache@v3 with: path: | ~/.gradle/caches @@ -142,7 +142,7 @@ jobs: run: ./gradlew clean lint${{ matrix.target }}Release --stacktrace - name: Upload ${{ matrix.target }} linting report if: always() - uses: actions/upload-artifact@v2 + uses: actions/upload-artifact@v3 with: name: release-lint-report-${{ matrix.target }} path: | diff --git a/.github/workflows/sync-from-external-sources.yml b/.github/workflows/sync-from-external-sources.yml index d390c47696..796d915ea6 100644 --- a/.github/workflows/sync-from-external-sources.yml +++ b/.github/workflows/sync-from-external-sources.yml @@ -23,7 +23,7 @@ jobs: - name: Run Emoji script run: ./tools/import_emojis.py - name: Create Pull Request for Emojis - uses: peter-evans/create-pull-request@v3 + uses: peter-evans/create-pull-request@v4 with: commit-message: Sync Emojis title: Sync Emojis @@ -49,7 +49,7 @@ jobs: - name: Run SAS String script run: ./tools/import_sas_strings.py - name: Create Pull Request for SAS Strings - uses: peter-evans/create-pull-request@v3 + uses: peter-evans/create-pull-request@v4 with: commit-message: Sync SAS Strings title: Sync SAS Strings @@ -68,7 +68,7 @@ jobs: - name: Run analytics import script run: ./tools/import_analytic_plan.sh - name: Create Pull Request for analytics plan - uses: peter-evans/create-pull-request@v3 + uses: peter-evans/create-pull-request@v4 with: commit-message: Sync analytics plan title: Sync analytics plan diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 587bf14488..3e8de8979c 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -21,11 +21,11 @@ jobs: cancel-in-progress: true steps: - uses: actions/checkout@v3 - - uses: actions/setup-java@v2 + - uses: actions/setup-java@v3 with: distribution: 'adopt' java-version: 11 - - uses: actions/cache@v2 + - uses: actions/cache@v3 with: path: | ~/.gradle/caches @@ -45,7 +45,7 @@ jobs: cancel-in-progress: true steps: - uses: actions/checkout@v3 - - uses: actions/cache@v2 + - uses: actions/cache@v3 with: path: | ~/.gradle/caches diff --git a/.github/workflows/triage-labelled.yml b/.github/workflows/triage-labelled.yml index eeddf2e785..82d5931ce7 100644 --- a/.github/workflows/triage-labelled.yml +++ b/.github/workflows/triage-labelled.yml @@ -11,7 +11,6 @@ jobs: if: > contains(github.event.issue.labels.*.name, 'A-Maths') || contains(github.event.issue.labels.*.name, 'A-Message-Pinning') || - contains(github.event.issue.labels.*.name, 'A-Threads') || contains(github.event.issue.labels.*.name, 'A-Polls') || contains(github.event.issue.labels.*.name, 'A-Location-Sharing') || contains(github.event.issue.labels.*.name, 'A-Message-Bubbles') || @@ -252,3 +251,30 @@ jobs: env: PROJECT_ID: "PN_kwDOAM0swc4AArk0" GITHUB_TOKEN: ${{ secrets.ELEMENT_BOT_TOKEN }} + + move_element_x_issues: + name: ElementX issues to ElementX project board + runs-on: ubuntu-latest + # Skip in forks + if: > + github.repository == 'vector-im/element-android' && + (contains(github.event.issue.labels.*.name, 'Z-ElementX-Alpha') || + contains(github.event.issue.labels.*.name, 'Z-ElementX-Beta') || + contains(github.event.issue.labels.*.name, 'Z-ElementX')) + steps: + - uses: octokit/graphql-action@v2.x + with: + headers: '{"GraphQL-Features": "projects_next_graphql"}' + query: | + mutation add_to_project($projectid:ID!,$contentid:ID!) { + addProjectNextItem(input:{projectId:$projectid contentId:$contentid}) { + projectNextItem { + id + } + } + } + projectid: ${{ env.PROJECT_ID }} + contentid: ${{ github.event.issue.node_id }} + env: + PROJECT_ID: "PN_kwDOAM0swc4ABTXY" + GITHUB_TOKEN: ${{ secrets.ELEMENT_BOT_TOKEN }} diff --git a/.github/workflows/triage-move-review-requests.yml b/.github/workflows/triage-move-review-requests.yml index 75738a53a9..61f1f114dd 100644 --- a/.github/workflows/triage-move-review-requests.yml +++ b/.github/workflows/triage-move-review-requests.yml @@ -7,6 +7,8 @@ jobs: add_design_pr_to_project: name: Move PRs asking for design review to the design board runs-on: ubuntu-latest + # Skip in forks + if: github.repository == 'vector-im/element-android' steps: - uses: octokit/graphql-action@v2.x id: find_team_members @@ -74,6 +76,8 @@ jobs: add_product_pr_to_project: name: Move PRs asking for product review to the product board runs-on: ubuntu-latest + # Skip in forks + if: github.repository == 'vector-im/element-android' steps: - uses: octokit/graphql-action@v2.x id: find_team_members diff --git a/.github/workflows/update-gradle-wrapper.yml b/.github/workflows/update-gradle-wrapper.yml index 1cbf29cc8d..63aaae15a5 100644 --- a/.github/workflows/update-gradle-wrapper.yml +++ b/.github/workflows/update-gradle-wrapper.yml @@ -13,6 +13,8 @@ jobs: - name: Update Gradle Wrapper uses: gradle-update/update-gradle-wrapper-action@v1 + # Skip in forks + if: github.repository == 'vector-im/element-android' with: repo-token: ${{ secrets.GITHUB_TOKEN }} target-branch: develop diff --git a/CHANGES.md b/CHANGES.md index c411593627..f952ec952a 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1,3 +1,193 @@ +Changes in Element 1.4.13 (2022-04-26) +====================================== + +Bugfixes 🐛 +---------- + - Fix UI freeze observed after each incremental sync ([#5835](https://github.com/vector-im/element-android/issues/5835)) + + +Changes in Element v1.4.12 (2022-04-20) +======================================= + +Features ✨ +---------- + - Add a setting to be able to always appear offline ([#5582](https://github.com/vector-im/element-android/issues/5582)) + - Adds the ability for audio attachments to be played in the timeline ([#5586](https://github.com/vector-im/element-android/issues/5586)) + - Do not cancel the current incremental sync request and treatment when the app goes to background ([#5719](https://github.com/vector-im/element-android/issues/5719)) + - Improve user experience when home servers do not yet support threads ([#5761](https://github.com/vector-im/element-android/issues/5761)) + +Bugfixes 🐛 +---------- + - Added text next to spinner when loading information after user is clicked on space members screen ([#4305](https://github.com/vector-im/element-android/issues/4305)) + - The string `ftue_auth_carousel_workplace_body` was declared not translatable by mistake ([#5262](https://github.com/vector-im/element-android/issues/5262)) + - Fix some cases where the read marker line would not show up ([#5475](https://github.com/vector-im/element-android/issues/5475)) + - Fix sometimes read marker not properly updating ([#5481](https://github.com/vector-im/element-android/issues/5481)) + - Fix sometimes endless loading timeline ([#5554](https://github.com/vector-im/element-android/issues/5554)) + - Use member name instead of room name in DM creation item ([#5570](https://github.com/vector-im/element-android/issues/5570)) + - Align auto-reporting of decryption errors implementation with web client. ([#5596](https://github.com/vector-im/element-android/issues/5596)) + - Choosing "leave all rooms and spaces" while leaving Space won't cause leaving DMs in this Space anymore ([#5609](https://github.com/vector-im/element-android/issues/5609)) + - Fixes display name being changed when using /myroomnick ([#5618](https://github.com/vector-im/element-android/issues/5618)) + - Fix endless loading if the event from a permalink is not found ([#5659](https://github.com/vector-im/element-android/issues/5659)) + - Redacted events are no longer visible. ([#5707](https://github.com/vector-im/element-android/issues/5707)) + - Don't wrongly show non-space invites in the space panel. ([#5731](https://github.com/vector-im/element-android/issues/5731)) + - Fixes the onboarding confetti rendering behind the content instead of in-front ([#5735](https://github.com/vector-im/element-android/issues/5735)) + - Fixes crash when navigating the app whilst processing new room keys ([#5746](https://github.com/vector-im/element-android/issues/5746)) + - Fix sorting of uploads in encrypted rooms ([#5757](https://github.com/vector-im/element-android/issues/5757)) + - Fixing setting transfer title in call transfer. ([#5765](https://github.com/vector-im/element-android/issues/5765)) + - Changes destination after joining a space to Explore Space Rooms screen ([#5766](https://github.com/vector-im/element-android/issues/5766)) + - Unignoring a user will perform an initial sync ([#5767](https://github.com/vector-im/element-android/issues/5767)) + - Open a room by link: use the actual roomId instead of the alias ([#5786](https://github.com/vector-im/element-android/issues/5786)) + +In development 🚧 +---------------- + - FTUE - Adds a new homeserver selection screen when creating an account ([#2396](https://github.com/vector-im/element-android/issues/2396)) + - FTUE - Updates the Captcha and T&Cs registration screens UI style ([#5279](https://github.com/vector-im/element-android/issues/5279)) + - FTUE - Adds error handling within the server selection screen ([#5749](https://github.com/vector-im/element-android/issues/5749)) + - Live Location Sharing - Send location data ([#5697](https://github.com/vector-im/element-android/issues/5697)) + - Live Location Sharing - Show message on start of a live ([#5710](https://github.com/vector-im/element-android/issues/5710)) + - Live Location Sharing - Attach location data to beacon info state event ([#5711](https://github.com/vector-im/element-android/issues/5711)) + - Live Location Sharing - Update beacon info state event when sharing is ended ([#5758](https://github.com/vector-im/element-android/issues/5758)) + + +SDK API changes ⚠️ +------------------ + - Include original event in live decryption listeners and update sync status naming to InitialSyncProgressing for clarity. ([#5639](https://github.com/vector-im/element-android/issues/5639)) + - KeysBackupService.getCurrentVersion takes a new type `KeysBackupLastVersionResult` in the callback. ([#5703](https://github.com/vector-im/element-android/issues/5703)) + - A lot of classes which were exposed to the clients and were located in the package `org.matrix.android.sdk.internal` have been moved to the package `org.matrix.android.sdk.api`. + All the classes which are in the package `org.matrix.android.sdk.internal` should now be declared `internal`. + Some unused code and classes have been removed. ([#5744](https://github.com/vector-im/element-android/issues/5744)) + - Some data classes are now immutable, using `val` instead of `var` ([#5762](https://github.com/vector-im/element-android/issues/5762)) + +Other changes +------------- + - Upgrade konfetti lib from 1.3.2 to 2.0.2 ([#5079](https://github.com/vector-im/element-android/issues/5079)) + - Spaces feedback section is removed from left panel ([#5486](https://github.com/vector-im/element-android/issues/5486)) + - Reduce error logs ([#5703](https://github.com/vector-im/element-android/issues/5703)) + - Adds a complete editor config file for our current code style ([#5727](https://github.com/vector-im/element-android/issues/5727)) + - Updates the posthog dev environment url and api key ([#5732](https://github.com/vector-im/element-android/issues/5732)) + - Setup Dokka to be able to generate documentation of the SDK module. Run `./gradlew matrix-sdk-android:dokkaHtml` to do it. ([#5744](https://github.com/vector-im/element-android/issues/5744)) + + +Changes in Element v1.4.11 (2022-04-07) +======================================= + +Bugfixes 🐛 +---------- + - Choosing "leave all rooms and spaces" while leaving Space won't cause leaving DMs in this Space anymore ([#5609](https://github.com/vector-im/element-android/issues/5609)) + + +Changes in Element v1.4.10 (2022-04-05) +======================================= + +Features ✨ +---------- + - Allow scrolling position of Voice Message playback ([#5426](https://github.com/vector-im/element-android/issues/5426)) + - Users will be able to provide feedback for threads ([#5647](https://github.com/vector-im/element-android/issues/5647)) + - Update Jitsi lib from 3.10.0 to 5.0.2 ([#5654](https://github.com/vector-im/element-android/issues/5654)) + +Bugfixes 🐛 +---------- + - Replace "open settings" button by "disable" action in RageShake dialog if there is no session ([#4445](https://github.com/vector-im/element-android/issues/4445)) + - Fixes room summaries showing encrypted content after verifying device ([#4867](https://github.com/vector-im/element-android/issues/4867)) + - Fixes polls being votable after being ended ([#5473](https://github.com/vector-im/element-android/issues/5473)) + - [Subscribing] Blank display name ([#5497](https://github.com/vector-im/element-android/issues/5497)) + - Fixes voice call button disappearing in DM rooms with more than 2 members ([#5548](https://github.com/vector-im/element-android/issues/5548)) + - Add loader in thread list ([#5562](https://github.com/vector-im/element-android/issues/5562)) + - Fixed key export when overwriting existing files ([#5663](https://github.com/vector-im/element-android/issues/5663)) + +In development 🚧 +---------------- + - Adding combined account creation and server selection screen as part of the new FTUE ([#5277](https://github.com/vector-im/element-android/issues/5277)) + - Finalising FTUE onboarding account creation personalization steps but keeping feature disabled until other parts are complete ([#5519](https://github.com/vector-im/element-android/issues/5519)) + - Live Location Sharing - Foreground Service and Notification ([#5595](https://github.com/vector-im/element-android/issues/5595)) + - Send beacon info state event when live location sharing started ([#5651](https://github.com/vector-im/element-android/issues/5651)) + - Show a banner in timeline while location sharing service is running ([#5660](https://github.com/vector-im/element-android/issues/5660)) + - Location sharing: adding possibility to choose duration of live sharing ([#5667](https://github.com/vector-im/element-android/issues/5667)) + +Other changes +------------- + - Improve main timeline thread summary rendering ([#5151](https://github.com/vector-im/element-android/issues/5151)) + - "Add space" copy is replaced with "create space" in left sliding panel ([#5516](https://github.com/vector-im/element-android/issues/5516)) + - Flattening the asynchronous onboarding state and passing all errors through the same pipeline ([#5517](https://github.com/vector-im/element-android/issues/5517)) + - Changed items order in space menu. Changed capitalization for "space" in those items ([#5524](https://github.com/vector-im/element-android/issues/5524)) + - Permalinks to root thread messages will now navigate you within the thread timeline ([#5567](https://github.com/vector-im/element-android/issues/5567)) + - Live location sharing: adding way to override feature activation in debug ([#5581](https://github.com/vector-im/element-android/issues/5581)) + - Adds unit tests around the login with matrix id flow ([#5628](https://github.com/vector-im/element-android/issues/5628)) + - Setup the plugin org.owasp.dependencycheck ([#5654](https://github.com/vector-im/element-android/issues/5654)) + - Implement threads beta opt-in mechanism to notify users about threads ([#5692](https://github.com/vector-im/element-android/issues/5692)) + + +Changes in Element v1.4.8 (2022-03-28) +====================================== + +Other changes +------------- + - Moving live location sharing permission to debug only builds whilst it is WIP ([#5636](https://github.com/vector-im/element-android/issues/5636)) + + +Changes in Element v1.4.7 (2022-03-24) +====================================== + +Bugfixes 🐛 +---------- + - Fix inconsistencies between the arrow visibility and the collapse action on the room sections ([#5616](https://github.com/vector-im/element-android/issues/5616)) + - Fix room list header count flickering + +Changes in Element v1.4.6 (2022-03-23) +====================================== + +Features ✨ +---------- + - Thread timeline is now live and much faster especially for large or old threads ([#5230](https://github.com/vector-im/element-android/issues/5230)) + - View all threads per room screen is now live when the home server supports threads ([#5232](https://github.com/vector-im/element-android/issues/5232)) + - Add a custom view to display a picker for share location options ([#5395](https://github.com/vector-im/element-android/issues/5395)) + - Add ability to pin a location on map for sharing ([#5417](https://github.com/vector-im/element-android/issues/5417)) + - Poll Integration Tests ([#5522](https://github.com/vector-im/element-android/issues/5522)) + - Live location sharing: adding build config field and show permission dialog ([#5536](https://github.com/vector-im/element-android/issues/5536)) + - Live location sharing: Adding indicator view when enabled ([#5571](https://github.com/vector-im/element-android/issues/5571)) + +Bugfixes 🐛 +---------- + - Poll system notifications on Android are not user friendly ([#4780](https://github.com/vector-im/element-android/issues/4780)) + - Add colors for shield vector drawable ([#4860](https://github.com/vector-im/element-android/issues/4860)) + - Support both stable and unstable prefixes for Events about Polls and Location ([#5340](https://github.com/vector-im/element-android/issues/5340)) + - Fix missing messages when loading messages forwards ([#5448](https://github.com/vector-im/element-android/issues/5448)) + - Fix presence indicator being aligned to the center of the room image ([#5489](https://github.com/vector-im/element-android/issues/5489)) + - Read receipt in wrong order ([#5514](https://github.com/vector-im/element-android/issues/5514)) + - Fix mentions using matrix.to rather than client defined permalink base url ([#5521](https://github.com/vector-im/element-android/issues/5521)) + - Fixes crash when tapping the timeline verification surround box instead of the buttons ([#5540](https://github.com/vector-im/element-android/issues/5540)) + - [Notification mode] Wrong mode is displayed when the mention only is selected on the web client ([#5547](https://github.com/vector-im/element-android/issues/5547)) + - Fix local echos not being shown when re-opening rooms ([#5551](https://github.com/vector-im/element-android/issues/5551)) + - Fix crash when closing a room while decrypting timeline events ([#5552](https://github.com/vector-im/element-android/issues/5552)) + - Fix sometimes read marker not properly updating ([#5564](https://github.com/vector-im/element-android/issues/5564)) + +In development 🚧 +---------------- + - Dynamically showing/hiding onboarding personalisation screens based on the users homeserver capabilities ([#5375](https://github.com/vector-im/element-android/issues/5375)) + - Introduces FTUE personalisation complete screen along with confetti celebration ([#5389](https://github.com/vector-im/element-android/issues/5389)) + +SDK API changes ⚠️ +------------------ + - Adds support for MSC3440, additional threads homeserver capabilities ([#5271](https://github.com/vector-im/element-android/issues/5271)) + +Other changes +------------- + - Refactoring for safer olm and megolm session usage ([#5380](https://github.com/vector-im/element-android/issues/5380)) + - Improve headers UI in Rooms/Messages lists ([#4533](https://github.com/vector-im/element-android/issues/4533)) + - Number of unread messages on space badge now include number of unread DMs ([#5260](https://github.com/vector-im/element-android/issues/5260)) + - Amend spaces menu to be consistent with iOS version ([#5270](https://github.com/vector-im/element-android/issues/5270)) + - Selected space highlight changed in left panel ([#5346](https://github.com/vector-im/element-android/issues/5346)) + - [Rooms list] Do not suggest collapse the unique section ([#5347](https://github.com/vector-im/element-android/issues/5347)) + - Add analytics support for threads ([#5378](https://github.com/vector-im/element-android/issues/5378)) + - Add top margin before our first message ([#5384](https://github.com/vector-im/element-android/issues/5384)) + - Improved onboarding registration unit test coverage ([#5408](https://github.com/vector-im/element-android/issues/5408)) + - Adds stable room hierarchy endpoint with a fallback to the unstable one ([#5443](https://github.com/vector-im/element-android/issues/5443)) + - Use ColorPrimary for attachmentGalleryButton tint ([#5501](https://github.com/vector-im/element-android/issues/5501)) + - Added online presence indicator attribute online to match offline styling ([#5513](https://github.com/vector-im/element-android/issues/5513)) + - Add a presence sync enabling build config ([#5563](https://github.com/vector-im/element-android/issues/5563)) + - Show stickers on click ([#5572](https://github.com/vector-im/element-android/issues/5572)) + + Changes in Element v1.4.4 (2022-03-09) ====================================== diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 2512052953..053931cac5 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -2,7 +2,7 @@ Please read https://github.com/matrix-org/synapse/blob/master/CONTRIBUTING.md -Android support can be found in this [![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) room. +Element Android support can be found in this room: [![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). # Specific rules for Matrix Android projects @@ -44,6 +44,8 @@ If you want to fix an issue in other languages, or add a missing translation, or ## I want to submit a PR to fix an issue +Please have a look in the [dedicated documentation](./docs/pull_request.md) about pull request. + Please check if a corresponding issue exists. If yes, please let us know in a comment that you're working on it. If an issue does not exist yet, it may be relevant to open a new issue and let us know that you're implementing it. @@ -142,6 +144,8 @@ Instead, please comment the original string with: ```xml ``` +And add `tools:ignore="UnusedResources"` to the string, to let lint ignore that the string is not used. + The string will be removed during the next sync with Weblate. ### Accessibility diff --git a/build.gradle b/build.gradle index 31416a0440..7a7f48d053 100644 --- a/build.gradle +++ b/build.gradle @@ -21,7 +21,8 @@ buildscript { classpath 'org.sonarsource.scanner.gradle:sonarqube-gradle-plugin:3.3' classpath 'com.google.android.gms:oss-licenses-plugin:0.10.5' classpath "com.likethesalad.android:stem-plugin:2.0.0" - + classpath 'org.owasp:dependency-check-gradle:7.1.0.1' + classpath "org.jetbrains.dokka:dokka-gradle-plugin:1.6.21" // NOTE: Do not place your application dependencies here; they belong // in the individual module build.gradle files } @@ -32,6 +33,16 @@ plugins { id "org.jlleitschuh.gradle.ktlint" version "10.2.1" } +// https://github.com/jeremylong/DependencyCheck +apply plugin: 'org.owasp.dependencycheck' + +dependencyCheck { + // See https://jeremylong.github.io/DependencyCheck/general/suppression.html + suppressionFiles = [ + "./tools/dependencycheck/suppressions.xml" + ] +} + allprojects { apply plugin: "org.jlleitschuh.gradle.ktlint" @@ -51,7 +62,7 @@ allprojects { } // Jitsi repo maven { - url "https://github.com/vector-im/jitsi_libre_maven/raw/main/android-sdk-3.10.0" + url "https://github.com/vector-im/jitsi_libre_maven/raw/main/android-sdk-5.0.2" // Note: to test Jitsi release you can use a local file like this: // url "file:///Users/bmarty/workspaces/jitsi_libre_maven/android-sdk-3.10.0" content { @@ -87,16 +98,33 @@ allprojects { // See https://github.com/JLLeitschuh/ktlint-gradle#configuration ktlint { + // See https://github.com/pinterest/ktlint/releases/ + version = "0.45.1" android = true ignoreFailures = false enableExperimentalRules = true // display the corresponding rule verbose = true disabledRules = [ + // TODO: Re-enable these 4 rules after reformatting project + "indent", + "experimental:argument-list-wrapping", + "max-line-length", + "parameter-list-wrapping", + "spacing-between-declarations-with-comments", "no-multi-spaces", "experimental:spacing-between-declarations-with-annotations", - "experimental:annotation" + "experimental:annotation", + // - Missing newline after "(" + // - Missing newline before ")" + "wrapping", + // - Unnecessary trailing comma before ")" + "experimental:trailing-comma", + // - A block comment in between other elements on the same line is disallowed + "experimental:comment-wrapping", + // - A KDoc comment after any other element on the same line must be separated by a new line + "experimental:kdoc-wrapping", ] } } diff --git a/changelog.d/4533.misc b/changelog.d/4533.misc deleted file mode 100644 index 1137a1c43c..0000000000 --- a/changelog.d/4533.misc +++ /dev/null @@ -1 +0,0 @@ -Improve headers UI in Rooms/Messages lists diff --git a/changelog.d/4780.bugfix b/changelog.d/4780.bugfix deleted file mode 100644 index 51eb1e4ad7..0000000000 --- a/changelog.d/4780.bugfix +++ /dev/null @@ -1 +0,0 @@ -Poll system notifications on Android are not user friendly \ No newline at end of file diff --git a/changelog.d/4860.bugfix b/changelog.d/4860.bugfix deleted file mode 100644 index 32049face4..0000000000 --- a/changelog.d/4860.bugfix +++ /dev/null @@ -1 +0,0 @@ -Add colors for shield vector drawable \ No newline at end of file diff --git a/changelog.d/5230.feature b/changelog.d/5230.feature deleted file mode 100644 index b333a3f2c7..0000000000 --- a/changelog.d/5230.feature +++ /dev/null @@ -1 +0,0 @@ -Thread timeline is now live and much faster especially for large or old threads \ No newline at end of file diff --git a/changelog.d/5232.feature b/changelog.d/5232.feature deleted file mode 100644 index 8f3bec97bd..0000000000 --- a/changelog.d/5232.feature +++ /dev/null @@ -1 +0,0 @@ -View all threads per room screen is now live when the home server supports threads \ No newline at end of file diff --git a/changelog.d/5260.misc b/changelog.d/5260.misc deleted file mode 100644 index 36812e2c83..0000000000 --- a/changelog.d/5260.misc +++ /dev/null @@ -1 +0,0 @@ -Number of unread messages on space badge now include number of unread DMs \ No newline at end of file diff --git a/changelog.d/5270.misc b/changelog.d/5270.misc deleted file mode 100644 index 9bbe41af59..0000000000 --- a/changelog.d/5270.misc +++ /dev/null @@ -1 +0,0 @@ -Amend spaces menu to be consistent with iOS version \ No newline at end of file diff --git a/changelog.d/5271.sdk b/changelog.d/5271.sdk deleted file mode 100644 index b73d97ee4f..0000000000 --- a/changelog.d/5271.sdk +++ /dev/null @@ -1 +0,0 @@ -Adds support for MSC3440, additional threads homeserver capabilities \ No newline at end of file diff --git a/changelog.d/5340.bugfix b/changelog.d/5340.bugfix deleted file mode 100644 index 4c53f0088c..0000000000 --- a/changelog.d/5340.bugfix +++ /dev/null @@ -1 +0,0 @@ -Support both stable and unstable prefixes for Events about Polls and Location \ No newline at end of file diff --git a/changelog.d/5346.misc b/changelog.d/5346.misc deleted file mode 100644 index f979c180ef..0000000000 --- a/changelog.d/5346.misc +++ /dev/null @@ -1 +0,0 @@ -Selected space highlight changed in left panel \ No newline at end of file diff --git a/changelog.d/5375.wip b/changelog.d/5375.wip deleted file mode 100644 index 352b2385a9..0000000000 --- a/changelog.d/5375.wip +++ /dev/null @@ -1 +0,0 @@ -Dynamically showing/hiding onboarding personalisation screens based on the users homeserver capabilities \ No newline at end of file diff --git a/changelog.d/5378.misc b/changelog.d/5378.misc deleted file mode 100644 index 1cf6da5e59..0000000000 --- a/changelog.d/5378.misc +++ /dev/null @@ -1 +0,0 @@ -Add analytics support for threads \ No newline at end of file diff --git a/changelog.d/5384.misc b/changelog.d/5384.misc deleted file mode 100644 index dca87422bb..0000000000 --- a/changelog.d/5384.misc +++ /dev/null @@ -1 +0,0 @@ -Add top margin before our first message diff --git a/changelog.d/5389.wip b/changelog.d/5389.wip deleted file mode 100644 index 089fe2da1a..0000000000 --- a/changelog.d/5389.wip +++ /dev/null @@ -1 +0,0 @@ -Introduces FTUE personalisation complete screen along with confetti celebration \ No newline at end of file diff --git a/changelog.d/5395.feature b/changelog.d/5395.feature deleted file mode 100644 index eb16c6cd81..0000000000 --- a/changelog.d/5395.feature +++ /dev/null @@ -1 +0,0 @@ -Add a custom view to display a picker for share location options diff --git a/changelog.d/5408.misc b/changelog.d/5408.misc deleted file mode 100644 index 3807ee1da8..0000000000 --- a/changelog.d/5408.misc +++ /dev/null @@ -1 +0,0 @@ -Improved onboarding registration unit test coverage \ No newline at end of file diff --git a/changelog.d/5417.feature b/changelog.d/5417.feature deleted file mode 100644 index 8b64f9fc7f..0000000000 --- a/changelog.d/5417.feature +++ /dev/null @@ -1 +0,0 @@ -Add ability to pin a location on map for sharing diff --git a/changelog.d/5443.misc b/changelog.d/5443.misc deleted file mode 100644 index f9fd715403..0000000000 --- a/changelog.d/5443.misc +++ /dev/null @@ -1 +0,0 @@ -Adds stable room hierarchy endpoint with a fallback to the unstable one diff --git a/changelog.d/5448.bugfix b/changelog.d/5448.bugfix deleted file mode 100644 index c4e8fb4a49..0000000000 --- a/changelog.d/5448.bugfix +++ /dev/null @@ -1 +0,0 @@ -Fix missing messages when loading messages forwards diff --git a/changelog.d/5501.misc b/changelog.d/5501.misc deleted file mode 100644 index 6c46a105b7..0000000000 --- a/changelog.d/5501.misc +++ /dev/null @@ -1 +0,0 @@ -Use ColorPrimary for attachmentGalleryButton tint \ No newline at end of file diff --git a/changelog.d/5514.bugfix b/changelog.d/5514.bugfix deleted file mode 100644 index 0dfbca6e9a..0000000000 --- a/changelog.d/5514.bugfix +++ /dev/null @@ -1 +0,0 @@ -Read receipt in wrong order \ No newline at end of file diff --git a/changelog.d/5521.bugfix b/changelog.d/5521.bugfix deleted file mode 100644 index 851396a770..0000000000 --- a/changelog.d/5521.bugfix +++ /dev/null @@ -1 +0,0 @@ -Fix mentions using matrix.to rather than client defined permalink base url diff --git a/changelog.d/5522.feature b/changelog.d/5522.feature deleted file mode 100644 index b50e8d1e60..0000000000 --- a/changelog.d/5522.feature +++ /dev/null @@ -1 +0,0 @@ -Poll Integration Tests \ No newline at end of file diff --git a/changelog.d/5536.feature b/changelog.d/5536.feature deleted file mode 100644 index bd0160f2fe..0000000000 --- a/changelog.d/5536.feature +++ /dev/null @@ -1 +0,0 @@ -Live location sharing: adding build config field and show permission dialog diff --git a/changelog.d/5540.bugfix b/changelog.d/5540.bugfix deleted file mode 100644 index 8887cf4074..0000000000 --- a/changelog.d/5540.bugfix +++ /dev/null @@ -1 +0,0 @@ -Fixes crash when tapping the timeline verification surround box instead of the buttons \ No newline at end of file diff --git a/changelog.d/5547.bugfix b/changelog.d/5547.bugfix deleted file mode 100644 index 3eb631902b..0000000000 --- a/changelog.d/5547.bugfix +++ /dev/null @@ -1 +0,0 @@ -[Notification mode] Wrong mode is displayed when the mention only is selected on the web client \ No newline at end of file diff --git a/changelog.d/5552.bugfix b/changelog.d/5552.bugfix deleted file mode 100644 index 5061e642f0..0000000000 --- a/changelog.d/5552.bugfix +++ /dev/null @@ -1 +0,0 @@ -Fix crash when closing a room while decrypting timeline events diff --git a/changelog.d/5563.misc b/changelog.d/5563.misc deleted file mode 100644 index c0867365f6..0000000000 --- a/changelog.d/5563.misc +++ /dev/null @@ -1 +0,0 @@ -Add a presence sync enabling build config diff --git a/changelog.d/5571.feature b/changelog.d/5571.feature deleted file mode 100644 index 04b62b8940..0000000000 --- a/changelog.d/5571.feature +++ /dev/null @@ -1 +0,0 @@ -Live location sharing: Adding indicator view when enabled diff --git a/changelog.d/5575.sdk b/changelog.d/5575.sdk new file mode 100644 index 0000000000..19339bdce2 --- /dev/null +++ b/changelog.d/5575.sdk @@ -0,0 +1,2 @@ +- Added registrationCustom into RegistrationWizard to send custom auth params for sign up +- Moved terms converter into api package to make it accessible in sdk \ No newline at end of file diff --git a/changelog.d/5652.bugfix b/changelog.d/5652.bugfix new file mode 100644 index 0000000000..8ebea1558f --- /dev/null +++ b/changelog.d/5652.bugfix @@ -0,0 +1 @@ +Tentative fix of images crashing when being sent or shared from gallery diff --git a/changelog.d/5772.feature b/changelog.d/5772.feature new file mode 100644 index 0000000000..85eec0a1ad --- /dev/null +++ b/changelog.d/5772.feature @@ -0,0 +1 @@ +Improve management of ignored users \ No newline at end of file diff --git a/changelog.d/5773.misc b/changelog.d/5773.misc new file mode 100644 index 0000000000..39c8b42073 --- /dev/null +++ b/changelog.d/5773.misc @@ -0,0 +1 @@ +Move "Ignored users" setting section into "Security & Privacy" \ No newline at end of file diff --git a/changelog.d/5774.misc b/changelog.d/5774.misc new file mode 100644 index 0000000000..795106381b --- /dev/null +++ b/changelog.d/5774.misc @@ -0,0 +1 @@ +Add a picto for ignored users in the room member list screen \ No newline at end of file diff --git a/changelog.d/5805.misc b/changelog.d/5805.misc new file mode 100644 index 0000000000..e0e6a311b4 --- /dev/null +++ b/changelog.d/5805.misc @@ -0,0 +1 @@ +Autoformats entire project diff --git a/changelog.d/5811.feature b/changelog.d/5811.feature new file mode 100644 index 0000000000..12111e323b --- /dev/null +++ b/changelog.d/5811.feature @@ -0,0 +1 @@ +VoIP Screen Sharing Permission \ No newline at end of file diff --git a/changelog.d/5812.sdk b/changelog.d/5812.sdk new file mode 100644 index 0000000000..5ddd8cfac1 --- /dev/null +++ b/changelog.d/5812.sdk @@ -0,0 +1 @@ +Move package `org.matrix.android.sdk.api.pushrules` to `org.matrix.android.sdk.api.session.pushrules` diff --git a/changelog.d/5814.feature b/changelog.d/5814.feature new file mode 100644 index 0000000000..c892702486 --- /dev/null +++ b/changelog.d/5814.feature @@ -0,0 +1 @@ +Live location sharing: updating beacon state event content structure diff --git a/changelog.d/5816.sdk b/changelog.d/5816.sdk new file mode 100644 index 0000000000..17233c66e5 --- /dev/null +++ b/changelog.d/5816.sdk @@ -0,0 +1,2 @@ +Some `Session` apis are now available by requesting the service first. For instance `Session.updateAvatar(...)` is now `Session.profileService().updateAvatar(...)` +The shortcut `Room.search()` has been removed, you have to use `Session.searchService().search()` diff --git a/changelog.d/5832.misc b/changelog.d/5832.misc new file mode 100644 index 0000000000..ace9dff5d4 --- /dev/null +++ b/changelog.d/5832.misc @@ -0,0 +1 @@ +Add a GH workflow to push ElementX issues to the global board. \ No newline at end of file diff --git a/changelog.d/5836.doc b/changelog.d/5836.doc new file mode 100644 index 0000000000..42073d66ef --- /dev/null +++ b/changelog.d/5836.doc @@ -0,0 +1 @@ +Update the PR process doc with 2 reviewers and a new reviewer team. \ No newline at end of file diff --git a/changelog.d/5847.bugfix b/changelog.d/5847.bugfix new file mode 100644 index 0000000000..acd13dec9a --- /dev/null +++ b/changelog.d/5847.bugfix @@ -0,0 +1 @@ +Fixes missing call icons when threads are enabled diff --git a/coverage.gradle b/coverage.gradle index 96881dfff2..b62ce0b4a0 100644 --- a/coverage.gradle +++ b/coverage.gradle @@ -17,7 +17,7 @@ def initializeReport(report, projects, classExcludes) { projects.each { project -> switch (project) { case { project.plugins.hasPlugin("com.android.application") }: - androidClassDirs.add("${project.buildDir}/tmp/kotlin-classes/debug") + androidClassDirs.add("${project.buildDir}/tmp/kotlin-classes/gplayDebug") androidSourceDirs.add("${project.projectDir}/src/main/kotlin") androidSourceDirs.add("${project.projectDir}/src/main/java") break diff --git a/dependencies.gradle b/dependencies.gradle index 1f2a08b6a6..7666a3bf9f 100644 --- a/dependencies.gradle +++ b/dependencies.gradle @@ -9,13 +9,13 @@ ext.versions = [ def gradle = "7.0.4" // Ref: https://kotlinlang.org/releases.html -def kotlin = "1.5.31" -def kotlinCoroutines = "1.5.2" +def kotlin = "1.6.0" +def kotlinCoroutines = "1.6.0" def dagger = "2.40.5" def retrofit = "2.9.0" def arrow = "0.8.2" def markwon = "4.6.2" -def moshi = "1.12.0" +def moshi = "1.13.0" def lifecycle = "2.4.0" def flowBinding = "1.2.0" def epoxy = "4.6.2" diff --git a/dependencies_groups.gradle b/dependencies_groups.gradle index 45883f506d..8422e05930 100644 --- a/dependencies_groups.gradle +++ b/dependencies_groups.gradle @@ -7,6 +7,7 @@ ext.groups = [ 'com.github.chrisbanes', 'com.github.hyuwah', 'com.github.jetradarmobile', + 'com.github.MatrixFrog', 'com.github.tapadoo', 'com.github.vector-im', 'com.github.yalantis', @@ -39,6 +40,7 @@ ext.groups = [ regex: [ ], group: [ + 'ch.qos.logback', 'com.adevinta.android', 'com.airbnb.android', 'com.almworks.sqlite4java', @@ -48,13 +50,19 @@ ext.groups = [ 'com.beust', 'com.davemorrissey.labs', 'com.dropbox.core', + 'com.soywiz.korlibs.korte', + 'com.facebook.fbjni', 'com.facebook.fresco', 'com.facebook.infer.annotation', 'com.facebook.soloader', 'com.facebook.stetho', + 'com.facebook.yoga', 'com.fasterxml', 'com.fasterxml.jackson', 'com.fasterxml.jackson.core', + 'com.fasterxml.jackson.dataformat', + 'com.fasterxml.jackson.module', + 'com.fasterxml.woodstox', 'com.gabrielittner.threetenbp', 'com.getkeepsafe.relinker', 'com.github.bumptech.glide', @@ -113,6 +121,7 @@ ext.groups = [ 'info.picocli', 'io.arrow-kt', 'io.github.detekt.sarif4k', + 'io.github.microutils', 'io.github.reactivecircus.flowbinding', 'io.grpc', 'io.jsonwebtoken', @@ -152,12 +161,15 @@ ext.groups = [ 'org.codehaus', 'org.codehaus.groovy', 'org.codehaus.mojo', + 'org.codehaus.woodstox', 'org.eclipse.ee4j', 'org.ec4j.core', + 'org.freemarker', 'org.glassfish.jaxb', 'org.hamcrest', 'org.jacoco', 'org.jetbrains', + 'org.jetbrains.dokka', 'org.jetbrains.intellij.deps', 'org.jetbrains.kotlin', 'org.jetbrains.kotlinx', @@ -203,4 +215,3 @@ ext.groups = [ ] ] ] - diff --git a/docs/jitsi.md b/docs/jitsi.md index 55cedaedb1..1b4e0a37b4 100644 --- a/docs/jitsi.md +++ b/docs/jitsi.md @@ -18,6 +18,8 @@ The generated maven repository is then host in the project https://github.com/ve Update the script `./tools/jitsi/build_jisti_libs.sh` with the tag of the project `https://github.com/jitsi/jitsi-meet`. +Latest tag can be found from this page: https://github.com/jitsi/jitsi-meet-release-notes/blob/master/CHANGELOG-MOBILE-SDKS.md + Currently we are building the version with the tag `android-sdk-3.10.0`. ### Run the build script diff --git a/docs/pull_request.md b/docs/pull_request.md new file mode 100644 index 0000000000..4775f292ee --- /dev/null +++ b/docs/pull_request.md @@ -0,0 +1,237 @@ +# Pull requests + +## 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. + +## Who should read this document? + +Every pull request reviewers, but also probably every ones who submit PRs. + +## Submitting PR + +### Who can submit pull requests? + +Basically every one who wants to contribute to the project! But there are some rules to follow. + +#### Humans + +People with write access to the project can directly clone the project, push their branches and create PR. + +External contributors must first fork the project and create PR to the mainline from there. + +##### Draft PR? + +Draft PR can be created when the submitter does not expect the PR to be reviewed and merged yet. It can be useful to publicly show the work, or to do a self-review first. + +Draft PR can also be created when it depends on other un-merged PR. + +In any case, it is better to explicitly declare in the description why the PR is a draft PR. + +Also, draft PR should not stay indefinitely in this state. It may be removed if it is the case and the submitter does not update it after a few days. + +##### 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: + +- The PR creator can assign specific people if they have another Android developer in their team or they think a specific reviewer should take a look at the PR. +- If there are missing reviewers, the PR creator assigns the [element-android-reviewers](https://github.com/orgs/vector-im/teams/element-android-reviewers) team as a reviewer. +- GitHub automatically assigns other reviewers. If one of the chosen reviewers is not available (holiday, etc.), remove them and set again the team, GitHub will select another reviewer. +- Reviewers get a notification to make the review: they review the code following the good practice (see the rest of this document). +- After making their own review, if they feel not confident enough, they can ask another person for a full review, or they can tag someone within a PR comment to check specific lines. + +For PRs coming from the community, the issue wrangler can assign either the team [element-android-reviewers](https://github.com/orgs/vector-im/teams/element-android-reviewers) or any members directly. + +##### PR review time + +As a PR submitter, you deserve a quick review. As a reviewer, you should do your best to unblock others. + +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 + +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. + +After this time, the submitter can ping the reviewer to get a status of the review. + +##### Re-request PR review + +Once all the remarks have been handled, it's possible to re-request a review from the (same) reviewer to let them know that the PR has been updated the PR is ready to be reviewed again. Use the double arrow next to the reviewer name to do that. + +##### When create split PR? + +To implement big new feature, it may be efficient to split the work into several smaller and scoped PRs. They will be easier to review, and they can be merged on `develop` faster. + +Big PR can take time, and there is a risk of future merge conflict. + +Feature flag can be used to avoid half implemented feature to be available in the application. + +That said, splitting into several PRs should not have the side effect to have more review to do, for instance if some code is added, then finally removed. + +##### Avoid fixing other unrelated issue in a big PR + +Each PR should focus on a single task. If other issues may be fixed when working in the area of it, it's preferable to open a dedicated PR. + +It will have the advantage to be reviewed and merged faster, and not interfere with the main PR. + +It's also applicable for code rework (such as renaming for instance), or code formatting. Sometimes, it is more efficient to extract that work to a dedicated PR, and rebase your branch once this "rework" PR has been merged. + +#### Bots + +Some bots can create PR, but they still have to be reviewed by the team + +##### Dependabot + +Dependabot is a tool which maintain all our external dependencies up to date. A dedicated PR is created for each new available release for one of our external dependency.Dependabot + +To review such PR, you have to + - **IMPORTANT** check the diff files (as always). + - Check the release note. Some existing bugs in Element project may be fixed by the upgrade + - Make sure that the CI is happy + - If the code does not compile (API break for instance), you have to checkout the branch and push new commits + - Do some smoke test, depending of the library which has been upgraded + +For some reason dependabot sometimes does not upgrade some dependencies. In this case, and when detected, the upgrade has to be done manually. + +##### Gradle wrapper + +`Update Gradle Wrapper` is a tool which can create PR to upgrade our gradle.properties file. +Review such PR is the same recipe than for PR from Dependabot + +##### Sync analytics plan + +This tools imports any update in the analytics plan. See instruction in the PR itself to handle it. +More info can be found in the file [analytics.md] + +## Reviewing PR + +### Who can review pull requests? + +As an open source project, every one can review each others PR. Of course an approval from internal developer is mandatory for a PR to be merged. +But comment in PR from the community are always appreciated! + +### What to have in mind when reviewing a PR + +1. User experience: is the UX and UI correct? You will probably be the second person to test the new thing, the first one is the developer. +2. Developer experience: does the code look nice and decoupled? No big functions, new classes added to the right module, etc. +3. Code maintenance. A bit similar to point 2. Tricky code must be documented for instance +4. Fork consideration. Will configuration of forks be easy? Some documentation may help in some cases. +5. We are building long term products. "Quick and dirty" code must be avoided. +6. The PR includes new tests for the added code, updated test for the existing code +7. All PRs from external contributors **MUST** include a sign-off. It's in the checklist, and sometimes it's checked by the submitter, but there is actually no sign-off. In this case, ask nicely for a sign-off and request changes (do not approve the PR, even if everything else is fine). + +### Rules + +#### Check the form + +##### PR title + +PR title should describe in one line what's brought by the PR. Reviewer can edit the title if it's not clear enough, or to add suffix like `[BLOCKED]` or similar. Fixing typo is also a good practice, since GitHub search is quite not efficient, so the words must be spelled without any issue. Adding suffix will help when viewing the PR list. + +It's free form, but prefix tags could also be used to help understand what's in the PR. + +Examples of prefixes: +- `[Refacto]` +- `[Feature]` +- `[Bugfix]` +- etc. + +Also, it's still possible to add labels to the PRs, such as `A-` or `T-` labels, even if this is not a string requirement. We prefer to spend time to add labels on issues. + +##### PR description + +PR description should follow the PR template, and at least provide some context about the code change. + +##### File change + +1. Code should follow the guidelines +2. Code should be formatted correctly +3. XML attribute must be sorted +4. New code is added at the correct location +5. New classes are added to the correct location +6. Naming is correct. Naming is really important, it's considered part of the documentation +7. Architecture is followed. For instance, the logic is in the ViewModel and not in the Fragment +8. There is at least one file for the changelog. Exception if the PR fixes something which has not been released yet. Changelog content should target their audience: `.sdk` extension are mainly targeted for developers, other extensions are targeted for users and forks maintainers. It should generally describe visual change rather than give technical details. More details can be found [here](../CONTRIBUTING.md#changelog). +9. PR includes tests. allScreensTest when applicable, and unit tests +10. Avoid over complicating things. Keep it simple (KISS)! +11. PR contains only the expected change. Sometimes, the diff is showing changes that are already on `develop`. This is not good, submitter has to fix that up. + +##### Check the commit + +Commit message must be short, one line and valuable. "WIP" is not a good commit message. Commit message can contain issue number, starting with `#`. GitHub will add some link between the issue and such commit, which can be useful. It's possible to change a commit message at any time (may require a force push). + +Commit messages can contain extra lines with more details, links, etc. But keep in mind that those lines are quite less visible than the first line. + +Also commit history should be nice. Having commits like "Adding temporary code" then later "Removing temporary code" is not good. The branch has to be rebased and those commit have to be dropped. + +PR merger could decide to squash and merge if commit history is not good. + +Commit like "Code review fixes" is good when reviewing the PR, since new changes can be reviewed easily, but is less valuable when looking at git history. To avoid this, PR submitter should always push new commits after a review (no commit amend with force push), and when the PR is approved decide to interactive rebase the PR to improve the git history and reduce noise. + +##### Check the substance + +1. Test the changes! +2. Test the nominal case and the edge cases +3. Run the sanity test for critical PR + +##### Make a dedicated meeting to review the PR + +Sometimes a big PR can be hard to review. Setting up a call with the PR submitter can speed up the communication, rather than putting comments and questions in GitHub comments. It has the inconvenience of making the discussion non-public, consider including a summary of the main points of the "offline" conversation in the PR. + +### What happen to the issue(s)? + +The issue(s) should be referenced in the PR description using keywords like `Closes` of `Fixes` followed by the issue number. + +Example: +> Closes #1 + +Note that you have to repeat the keyword in case of a list of issue + +> Closes #1, Closes #2, etc. + +When PR will be merged, such referenced issue will be automatically closed. +It is up to the person who has merged the PR to go to the (closed) issue(s) and to add a comment to inform in which version the issue fix will be available. Use the current version of `develop` branch. + +> Closed in Element Android v1.x.y + +### Merge conflict + +It's up to the submitter to handle merge conflict. Sometimes, they can be fixed directly from GitHub, sometimes this is not possible. The branch can be rebased on `develop`, or the `develop` branch can be merged on the branch, it's up to the submitter to decide what is best. +Keep in mind that Github Actions are not run in case of conflict. + +### When and who can merge PR + +PR can be merged by the submitter, if and only if at least one approval from another developer is done. Approval from all people added as reviewer is also a good thing to have. Approval from design team may be mandatory, but is not sufficient to merge a PR. + +PR can also be merged by the reviewer, to reduce the time the PR is open. But only if the PR is not in draft and the change are quite small, or behind a feature flag. + +Dangerous PR should not be merged just before a release. Dangerous PR are PR that could break the app. Update of Realm library, rework in the chunk of Events management in the SDK, etc. + +We prefer to merge such PR after a release so that it can be tested during several days by the team before behind included in a release candidate. + +PR from bots will always be merged by the reviewer, right after approving the changes, or in case of critical changes, right after a release. + +#### Merge type + +Generally we use "Create a merge commit", which has the advantage to keep the branch visible. + +If git history is noisy (code added, then removed, etc.), it's possible to use "Squash and merge". But the branch will not be visible anymore, a commit will be added on top of develop. Git commit message can (and probably must) be edited from the GitHub web app. It's better if the submitter do the work to cleanup the git history by using a git interactive rebase of their branch. + +### Resolve conversation + +Generally we do not close conversation added during PR review and update by clicking on "Resolve conversation" +If the submitter or the reviewer do so, it will more difficult for further readers to see again the content. They will have to open the conversation to see it again. it's a waste of time. + +When remarks are handled, a small comment like "done" is enough, commit hash can also be added to the conversation. + +Exception: for big PRs with lots of conversations, using "Resolve conversation" may help to see the remaining remarks. + +Also "Resolve conversation" should probably be hit by the creator of the conversation. + +## Responsibility + +PR submitter is responsible of the incoming change. PR reviewers who approved the PR take a part of responsibility on the code which will land to develop, and then be used by our users, and the user of our forks. + +That said, bug may still be merged on `develop`, this is still acceptable of course. In this case, please make sure an issue is created and correctly labelled. Ideally, such issues should be fixed before the next release candidate, i.e. with a higher priority. But as we release the application every 10 working days, it can be hard to fix every bugs. That's why PR should be fully tested and reviewed before being merge and we should never comment code review remark with "will be handled later", or similar comments. \ No newline at end of file diff --git a/fastlane/metadata/android/cs-CZ/changelogs/40104040.txt b/fastlane/metadata/android/cs-CZ/changelogs/40104040.txt new file mode 100644 index 0000000000..38a29f5a5d --- /dev/null +++ b/fastlane/metadata/android/cs-CZ/changelogs/40104040.txt @@ -0,0 +1,2 @@ +Hlavní změny v této verzi: vylepšení indikátoru psaní. Opravy různých chyb a vylepšení stability. +Úplný seznam změn: https://github.com/vector-im/element-android/releases/tag/v1.4.4 diff --git a/fastlane/metadata/android/cs-CZ/changelogs/40104060.txt b/fastlane/metadata/android/cs-CZ/changelogs/40104060.txt new file mode 100644 index 0000000000..3eda022464 --- /dev/null +++ b/fastlane/metadata/android/cs-CZ/changelogs/40104060.txt @@ -0,0 +1,2 @@ +Hlavní změny v této verzi: Časová osa vláken je nyní živá a rychlejší. Opravy různých chyb a vylepšení stability. +Úplný seznam změn: https://github.com/vector-im/element-android/releases/tag/v1.4.6 diff --git a/fastlane/metadata/android/cs-CZ/changelogs/40104070.txt b/fastlane/metadata/android/cs-CZ/changelogs/40104070.txt new file mode 100644 index 0000000000..cba2012c1c --- /dev/null +++ b/fastlane/metadata/android/cs-CZ/changelogs/40104070.txt @@ -0,0 +1,2 @@ +Hlavní změny v této verzi: Opravy různých chyb a vylepšení stability. +Úplný seznam změn: https://github.com/vector-im/element-android/releases/tag/v1.4.7 diff --git a/fastlane/metadata/android/cs-CZ/changelogs/40104080.txt b/fastlane/metadata/android/cs-CZ/changelogs/40104080.txt new file mode 100644 index 0000000000..61e7fd7940 --- /dev/null +++ b/fastlane/metadata/android/cs-CZ/changelogs/40104080.txt @@ -0,0 +1,2 @@ +Hlavní změny v této verzi: Časová osa vlákna je nyní živá a rychlejší. Opravy různých chyb a vylepšení stability. +Úplný seznam změn: https://github.com/vector-im/element-android/releases diff --git a/fastlane/metadata/android/de-DE/changelogs/40104040.txt b/fastlane/metadata/android/de-DE/changelogs/40104040.txt new file mode 100644 index 0000000000..8813f7f3c3 --- /dev/null +++ b/fastlane/metadata/android/de-DE/changelogs/40104040.txt @@ -0,0 +1,2 @@ +Neuer Tippindikator und Bugfixes +Alle Änderungen: https://github.com/vector-im/element-android/releases/tag/v1.4.4 diff --git a/fastlane/metadata/android/en-US/changelogs/40104060.txt b/fastlane/metadata/android/en-US/changelogs/40104060.txt new file mode 100644 index 0000000000..1863bef5fb --- /dev/null +++ b/fastlane/metadata/android/en-US/changelogs/40104060.txt @@ -0,0 +1,2 @@ +Main changes in this version: Thread timeline are now live and faster. Various bug fixes and stability improvements. +Full changelog: https://github.com/vector-im/element-android/releases/tag/v1.4.6 \ No newline at end of file diff --git a/fastlane/metadata/android/en-US/changelogs/40104070.txt b/fastlane/metadata/android/en-US/changelogs/40104070.txt new file mode 100644 index 0000000000..99a3ecfe7b --- /dev/null +++ b/fastlane/metadata/android/en-US/changelogs/40104070.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/tag/v1.4.7 \ No newline at end of file diff --git a/fastlane/metadata/android/en-US/changelogs/40104080.txt b/fastlane/metadata/android/en-US/changelogs/40104080.txt new file mode 100644 index 0000000000..66ed1664bd --- /dev/null +++ b/fastlane/metadata/android/en-US/changelogs/40104080.txt @@ -0,0 +1,2 @@ +Main changes in this version: Thread timeline are now live and faster. Various bug fixes and stability improvements. +Full changelog: https://github.com/vector-im/element-android/releases \ No newline at end of file diff --git a/fastlane/metadata/android/en-US/changelogs/40104100.txt b/fastlane/metadata/android/en-US/changelogs/40104100.txt new file mode 100644 index 0000000000..1cfb360e11 --- /dev/null +++ b/fastlane/metadata/android/en-US/changelogs/40104100.txt @@ -0,0 +1,2 @@ +Main changes in this version: Scroll in voice message. Various bug fixes and stability improvements. +Full changelog: https://github.com/vector-im/element-android/releases \ No newline at end of file diff --git a/fastlane/metadata/android/en-US/changelogs/40104110.txt b/fastlane/metadata/android/en-US/changelogs/40104110.txt new file mode 100644 index 0000000000..217bbe0b39 --- /dev/null +++ b/fastlane/metadata/android/en-US/changelogs/40104110.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 \ No newline at end of file diff --git a/fastlane/metadata/android/en-US/changelogs/40104120.txt b/fastlane/metadata/android/en-US/changelogs/40104120.txt new file mode 100644 index 0000000000..ea188c101c --- /dev/null +++ b/fastlane/metadata/android/en-US/changelogs/40104120.txt @@ -0,0 +1,2 @@ +Main changes in this version: Allows users to appear offline and adds an audio player for audio attachments +Full changelog: https://github.com/vector-im/element-android/releases diff --git a/fastlane/metadata/android/en-US/changelogs/40104130.txt b/fastlane/metadata/android/en-US/changelogs/40104130.txt new file mode 100644 index 0000000000..ea188c101c --- /dev/null +++ b/fastlane/metadata/android/en-US/changelogs/40104130.txt @@ -0,0 +1,2 @@ +Main changes in this version: Allows users to appear offline and adds an audio player for audio attachments +Full changelog: https://github.com/vector-im/element-android/releases diff --git a/fastlane/metadata/android/es-ES/changelogs/40104000.txt b/fastlane/metadata/android/es-ES/changelogs/40104000.txt new file mode 100644 index 0000000000..ea607fe19a --- /dev/null +++ b/fastlane/metadata/android/es-ES/changelogs/40104000.txt @@ -0,0 +1,2 @@ +Principales cambios de esta versión: primera implementación de los hilos de mensajes. Burbujas de mensajes. +Todos los cambios en: https://github.com/vector-im/element-android/releases/tag/v1.4.0 diff --git a/fastlane/metadata/android/es-ES/changelogs/40104020.txt b/fastlane/metadata/android/es-ES/changelogs/40104020.txt new file mode 100644 index 0000000000..8c2c78cb62 --- /dev/null +++ b/fastlane/metadata/android/es-ES/changelogs/40104020.txt @@ -0,0 +1,2 @@ +Principales cambios de esta versión: añadir @room, encuestas cerradas y muchos cambios menores más. +Todos los cambios en: https://github.com/vector-im/element-android/releases/tag/v1.4.2 diff --git a/fastlane/metadata/android/et/changelogs/40104040.txt b/fastlane/metadata/android/et/changelogs/40104040.txt new file mode 100644 index 0000000000..b4a0e059e3 --- /dev/null +++ b/fastlane/metadata/android/et/changelogs/40104040.txt @@ -0,0 +1,2 @@ +Põhilised muutused selles versioonis: kirjutusteatiste liidese uuendused ning pisiparandused ja stabiilsust parandavad kohendused. +Kogu ingliskeelne muudatuste logi: https://github.com/vector-im/element-android/releases/tag/v1.4.4 diff --git a/fastlane/metadata/android/et/changelogs/40104060.txt b/fastlane/metadata/android/et/changelogs/40104060.txt new file mode 100644 index 0000000000..f506b617ed --- /dev/null +++ b/fastlane/metadata/android/et/changelogs/40104060.txt @@ -0,0 +1,2 @@ +Põhilised muutused selles versioonis: jutulõngad on nüüd kasutatavad ja toimivad kiiremini, lisaks pisiparandused ja stabiilsust parandavad kohendused. +Kogu ingliskeelne muudatuste logi: https://github.com/vector-im/element-android/releases/tag/v1.4.6 diff --git a/fastlane/metadata/android/et/changelogs/40104070.txt b/fastlane/metadata/android/et/changelogs/40104070.txt new file mode 100644 index 0000000000..ea3582678d --- /dev/null +++ b/fastlane/metadata/android/et/changelogs/40104070.txt @@ -0,0 +1,2 @@ +Põhilised muutused selles versioonis: pisiparandused ja stabiilsust parandavad kohendused. +Kogu ingliskeelne muudatuste logi: https://github.com/vector-im/element-android/releases/tag/v1.4.7 diff --git a/fastlane/metadata/android/et/changelogs/40104080.txt b/fastlane/metadata/android/et/changelogs/40104080.txt new file mode 100644 index 0000000000..a35fded9b6 --- /dev/null +++ b/fastlane/metadata/android/et/changelogs/40104080.txt @@ -0,0 +1,2 @@ +Põhilised muutused selles versioonis: jutulõngad on nüüd kasutatavad ja toimivad kiiremini, lisaks pisiparandused ja stabiilsust parandavad kohendused. +Kogu ingliskeelne muudatuste logi: https://github.com/vector-im/element-android/releases diff --git a/fastlane/metadata/android/fa/changelogs/40104000.txt b/fastlane/metadata/android/fa/changelogs/40104000.txt new file mode 100644 index 0000000000..7beb79981f --- /dev/null +++ b/fastlane/metadata/android/fa/changelogs/40104000.txt @@ -0,0 +1,2 @@ +تغییرات اصلی در این نگارش: پیاده سازی نخستین پیام‌های رشته‌ای. حباب‌های پیام. +گزارش دگرگونی کامل: https://github.com/vector-im/element-android/releases/tag/v1.4.0 diff --git a/fastlane/metadata/android/fa/changelogs/40104020.txt b/fastlane/metadata/android/fa/changelogs/40104020.txt new file mode 100644 index 0000000000..6d5148220d --- /dev/null +++ b/fastlane/metadata/android/fa/changelogs/40104020.txt @@ -0,0 +1,2 @@ +تغییرات اصلی در این نگارش: افزودن پشتیبانی به ‪@room‬ و نظرسنجی‌های فاش نشده در کنار تغییرات کوچک دیگر. +گزارش دگرگونی کامل: https://github.com/vector-im/element-android/releases/tag/v1.4.2 diff --git a/fastlane/metadata/android/fa/changelogs/40104040.txt b/fastlane/metadata/android/fa/changelogs/40104040.txt new file mode 100644 index 0000000000..6e8f4911ea --- /dev/null +++ b/fastlane/metadata/android/fa/changelogs/40104040.txt @@ -0,0 +1,2 @@ +تغییرات عمده در این نگارش: به‌روز رسانی‌های رابط کاربری نشانگر نوشتن. چندین رفع اشکال و بهبودهای پایداری‌. +گزارش دگرگونی کامل: https://github.com/vector-im/element-android/releases/tag/v1.4.4 diff --git a/fastlane/metadata/android/fa/changelogs/40104060.txt b/fastlane/metadata/android/fa/changelogs/40104060.txt new file mode 100644 index 0000000000..5a1188b370 --- /dev/null +++ b/fastlane/metadata/android/fa/changelogs/40104060.txt @@ -0,0 +1,2 @@ +تغییرات عمده در این نگارش: خط زمانی رشته‌ها اکنون زنده و سریع‌تر است. چندین رفع اشکال و بهبود پایداری. +گزارش دگرگونی کامل: https://github.com/vector-im/element-android/releases/tag/v1.4.6 diff --git a/fastlane/metadata/android/fa/changelogs/40104070.txt b/fastlane/metadata/android/fa/changelogs/40104070.txt new file mode 100644 index 0000000000..d35ded337b --- /dev/null +++ b/fastlane/metadata/android/fa/changelogs/40104070.txt @@ -0,0 +1,2 @@ +تغییرات عمده در این نگارش: چندین رفع اشکال و بهبود پایداری. +گزارش دگرگونی کامل: https://github.com/vector-im/element-android/releases/tag/v1.4.7 diff --git a/fastlane/metadata/android/fa/changelogs/40104080.txt b/fastlane/metadata/android/fa/changelogs/40104080.txt new file mode 100644 index 0000000000..673a46a10d --- /dev/null +++ b/fastlane/metadata/android/fa/changelogs/40104080.txt @@ -0,0 +1,2 @@ +تغییرات عمده در این نگارش: خط زمانی رشته‌ها اکنون زنده و سریع‌تر است. چندین رفع اشکال و بهبود پایداری. +گزارش دگرگونی کامل: https://github.com/vector-im/element-android/releases diff --git a/fastlane/metadata/android/fr-FR/changelogs/40104040.txt b/fastlane/metadata/android/fr-FR/changelogs/40104040.txt new file mode 100644 index 0000000000..af2d7bb086 --- /dev/null +++ b/fastlane/metadata/android/fr-FR/changelogs/40104040.txt @@ -0,0 +1,2 @@ +Principaux changements pour cette version : Mise-à-jour de l’interface de notification de rédaction en cours. Plusieurs corrections de bogues et d’améliorations de stabilité. +Intégralité des changements : https://github.com/vector-im/element-android/releases/tag/v1.4.4 diff --git a/fastlane/metadata/android/fr-FR/changelogs/40104060.txt b/fastlane/metadata/android/fr-FR/changelogs/40104060.txt new file mode 100644 index 0000000000..24e0fde3f3 --- /dev/null +++ b/fastlane/metadata/android/fr-FR/changelogs/40104060.txt @@ -0,0 +1,2 @@ +Principaux changements pour cette version : Les fils de discussion sont officiellement disponibles, et plus rapides. Plusieurs corrections de bogues et d’améliorations de stabilité. +Intégralité des changements : https://github.com/vector-im/element-android/releases/tag/v1.4.6 diff --git a/fastlane/metadata/android/fr-FR/changelogs/40104070.txt b/fastlane/metadata/android/fr-FR/changelogs/40104070.txt new file mode 100644 index 0000000000..3f9879d917 --- /dev/null +++ b/fastlane/metadata/android/fr-FR/changelogs/40104070.txt @@ -0,0 +1,2 @@ +Principaux changements pour cette version : Plusieurs corrections de bogues et d’améliorations de stabilité. +Intégralité des changements : https://github.com/vector-im/element-android/releases/tag/v1.4.7 diff --git a/fastlane/metadata/android/fr-FR/changelogs/40104080.txt b/fastlane/metadata/android/fr-FR/changelogs/40104080.txt new file mode 100644 index 0000000000..84f92c18e2 --- /dev/null +++ b/fastlane/metadata/android/fr-FR/changelogs/40104080.txt @@ -0,0 +1,2 @@ +Principaux changements pour cette version : Les fils de discussion sont officiellement disponibles, et plus rapides. Plusieurs corrections de bogues et d’améliorations de stabilité. +Intégralité des changements : https://github.com/vector-im/element-android/releases diff --git a/fastlane/metadata/android/hu-HU/changelogs/40104040.txt b/fastlane/metadata/android/hu-HU/changelogs/40104040.txt new file mode 100644 index 0000000000..60ec5256aa --- /dev/null +++ b/fastlane/metadata/android/hu-HU/changelogs/40104040.txt @@ -0,0 +1,2 @@ +Fő változás ebben a verzióban: Gépelés visszajelzési frissítések a felületen. További hibajavítások egy stabilitást növelő fejlesztések. +Teljes változásnapló: https://github.com/vector-im/element-android/releases/tag/v1.4.4 diff --git a/fastlane/metadata/android/hu-HU/changelogs/40104060.txt b/fastlane/metadata/android/hu-HU/changelogs/40104060.txt new file mode 100644 index 0000000000..a3f4b89d92 --- /dev/null +++ b/fastlane/metadata/android/hu-HU/changelogs/40104060.txt @@ -0,0 +1,2 @@ +Fő változás ebben a verzióban: Megjelentek az üzenetszálak az idővonalon és gyorsak. További hibajavítások és stabilitási fejlesztések. +Teljes változásnapló: https://github.com/vector-im/element-android/releases/tag/v1.4.6 diff --git a/fastlane/metadata/android/hu-HU/changelogs/40104070.txt b/fastlane/metadata/android/hu-HU/changelogs/40104070.txt new file mode 100644 index 0000000000..0f969fb577 --- /dev/null +++ b/fastlane/metadata/android/hu-HU/changelogs/40104070.txt @@ -0,0 +1,2 @@ +Fő változás ebben a verzióban: Hibajavítások és stabilizációs fejlesztések. +Teljes változásnapló: https://github.com/vector-im/element-android/releases/tag/v1.4.7 diff --git a/fastlane/metadata/android/hu-HU/changelogs/40104080.txt b/fastlane/metadata/android/hu-HU/changelogs/40104080.txt new file mode 100644 index 0000000000..c29b20c216 --- /dev/null +++ b/fastlane/metadata/android/hu-HU/changelogs/40104080.txt @@ -0,0 +1,2 @@ +Fő változás ebben a verzióban: Megjelentek az üzenetszálak az idővonalon és gyorsak. További hibajavítások és stabilitási fejlesztések. +Teljes változásnapló: https://github.com/vector-im/element-android/releases diff --git a/fastlane/metadata/android/hu-HU/full_description.txt b/fastlane/metadata/android/hu-HU/full_description.txt index 0791eed7ba..b43613eb20 100644 --- a/fastlane/metadata/android/hu-HU/full_description.txt +++ b/fastlane/metadata/android/hu-HU/full_description.txt @@ -8,12 +8,13 @@ Az Element egy biztonságos üzenetküldő, és egy csapatmunka app, amely távo - Videochat, VoIP, és képernyőmegosztási lehetőséggel - Egyszerű integráció a kedvenc online kollaborációs eszközeiddel, projektkezelési eszközökkel, VoIP szolgáltatásokkal, és más csoportos üzenetküldő alkalmazásokkal -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. +Az Element teljesen más, mint az összes többi üzenetküldő és kollaborációs alkalmazás. A biztonságos üzenetküldést és decentralizált kommunikációt biztosító Matrix platformot használja. Akár egyénileg üzemeltetett szervereket is lehet használni az adatok teljes kontrollálása érdekében. -Privacy and encrypted messaging -Element protects you from unwanted ads, data mining and walled gardens. It also secures all your data, one-to-one video and voice communication through end-to-end encryption and cross-signed device verification. +Magánszféra és titkosított csevegés +Az Element megvéd a nemkívánatos hirdetésektől, adatbányászattól, és a zárt platformoktól. Ezeken felül biztonságban tartja az összes adatod és 1:1 hívásod a végponti titkosításnak és az eszközök-közti hitelesítésnek köszönhetően. + +Az Element átadja neked az irányítást a magánszférád felett, miközben lehetővé teszi, hogy biztonságosan kommunikálj bárkivel a Matrix hálózatban, vagy a többi üzleti kommunikációs eszközt használókkal, az olyan appok integrálásának köszönhetően, mint például a Slack. -Element gives you control over your privacy while allowing you to communicate securely with anyone 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. diff --git a/fastlane/metadata/android/id/changelogs/40104040.txt b/fastlane/metadata/android/id/changelogs/40104040.txt new file mode 100644 index 0000000000..4f3ef77ed6 --- /dev/null +++ b/fastlane/metadata/android/id/changelogs/40104040.txt @@ -0,0 +1,2 @@ +Perubahan utama dalam versi ini: pembaruan UI indikator pengetikan. Beberapa perbaikan kutu dan perbaikan stabilitas. +Catatan perubahan lanjutan: https://github.com/vector-im/element-android/releases/tag/v1.4.4 diff --git a/fastlane/metadata/android/id/changelogs/40104060.txt b/fastlane/metadata/android/id/changelogs/40104060.txt new file mode 100644 index 0000000000..3b4ce82497 --- /dev/null +++ b/fastlane/metadata/android/id/changelogs/40104060.txt @@ -0,0 +1,2 @@ +Perubahan utama dalam versi ini: Linimasa utasan sekarang langsung dan lebih cepat. Banyak perbaikan kutu dan perbaikan stabilitas. +Catatan perubahan lanjutan: https://github.com/vector-im/element-android/releases/tag/v1.4.6 diff --git a/fastlane/metadata/android/id/changelogs/40104070.txt b/fastlane/metadata/android/id/changelogs/40104070.txt new file mode 100644 index 0000000000..39daf31eb6 --- /dev/null +++ b/fastlane/metadata/android/id/changelogs/40104070.txt @@ -0,0 +1,2 @@ +Perubahan utama dalam versi ini: Banyak perbaikan kutu dan perbaikan stabilitas. +Catatan perubahan lanjutan: https://github.com/vector-im/element-android/releases/tag/v1.4.7 diff --git a/fastlane/metadata/android/id/changelogs/40104080.txt b/fastlane/metadata/android/id/changelogs/40104080.txt new file mode 100644 index 0000000000..0a5ae82096 --- /dev/null +++ b/fastlane/metadata/android/id/changelogs/40104080.txt @@ -0,0 +1,2 @@ +Perubahan utama dalam versi ini: Linimasa utasan sekarang langsung dan lebih cepat. Banyak perbaikan kutu dan perbaikan stabilitas. +Catatan perubahan lanjutan: https://github.com/vector-im/element-android/releases diff --git a/fastlane/metadata/android/it-IT/changelogs/40104040.txt b/fastlane/metadata/android/it-IT/changelogs/40104040.txt new file mode 100644 index 0000000000..ef63e614cb --- /dev/null +++ b/fastlane/metadata/android/it-IT/changelogs/40104040.txt @@ -0,0 +1,2 @@ +Modifiche principali in questa versione: agg.mento indicatore scrittura. Correzioni errori e miglioramenti stabilità. +Cronologia completa: https://github.com/vector-im/element-android/releases/tag/v1.4.4 diff --git a/fastlane/metadata/android/it-IT/changelogs/40104060.txt b/fastlane/metadata/android/it-IT/changelogs/40104060.txt new file mode 100644 index 0000000000..c9ea8af2cc --- /dev/null +++ b/fastlane/metadata/android/it-IT/changelogs/40104060.txt @@ -0,0 +1,2 @@ +Modifiche principali in questa versione: la linea temporale per argomenti è ora attiva e più veloce. Varie correzioni e miglioramenti. +Cronologia completa: https://github.com/vector-im/element-android/releases/tag/v1.4.6 diff --git a/fastlane/metadata/android/it-IT/changelogs/40104070.txt b/fastlane/metadata/android/it-IT/changelogs/40104070.txt new file mode 100644 index 0000000000..2dfd415920 --- /dev/null +++ b/fastlane/metadata/android/it-IT/changelogs/40104070.txt @@ -0,0 +1,2 @@ +Modifiche principali in questa versione: varie correzioni di errori e miglioramenti della stabilità. +Cronologia completa: https://github.com/vector-im/element-android/releases/tag/v1.4.7 diff --git a/fastlane/metadata/android/it-IT/changelogs/40104080.txt b/fastlane/metadata/android/it-IT/changelogs/40104080.txt new file mode 100644 index 0000000000..8427e3d3fe --- /dev/null +++ b/fastlane/metadata/android/it-IT/changelogs/40104080.txt @@ -0,0 +1,2 @@ +Modifiche principali in questa versione: la linea temporale per argomenti è ora attiva e più veloce. Varie correzioni e miglioramenti. +Cronologia completa: https://github.com/vector-im/element-android/releases diff --git a/fastlane/metadata/android/pl-PL/changelogs/40103060.txt b/fastlane/metadata/android/pl-PL/changelogs/40103060.txt new file mode 100644 index 0000000000..9dc1e9e297 --- /dev/null +++ b/fastlane/metadata/android/pl-PL/changelogs/40103060.txt @@ -0,0 +1,2 @@ +Główne zmiany w tej wersji: Dodanie obsługi obecności, dla pokoju wiadomości bezpośrednich (uwaga: obecność jest wyłączona na matrix.org). Dodano ponownie obsługę Android Auto. +Pełna lista zmian: https://github.com/vector-im/element-android/releases/tag/v1.3.6 diff --git a/fastlane/metadata/android/pl-PL/changelogs/40103070.txt b/fastlane/metadata/android/pl-PL/changelogs/40103070.txt index 2cb20a9570..08d9d2193e 100644 --- a/fastlane/metadata/android/pl-PL/changelogs/40103070.txt +++ b/fastlane/metadata/android/pl-PL/changelogs/40103070.txt @@ -1,2 +1,2 @@ -Główne zmiany w tej wersji: Poprawki błędów dotyczące głównie powiadomień -Pełny changelog: https://github.com/vector-im/element-android/releases/tag/v1.3.7 +Główne zmiany w tej wersji: Poprawki błędów dotyczące głównie powiadomień. +Pełna lista zmian: https://github.com/vector-im/element-android/releases/tag/v1.3.7-RC2 diff --git a/fastlane/metadata/android/pl-PL/changelogs/40103080.txt b/fastlane/metadata/android/pl-PL/changelogs/40103080.txt new file mode 100644 index 0000000000..c850479f59 --- /dev/null +++ b/fastlane/metadata/android/pl-PL/changelogs/40103080.txt @@ -0,0 +1,2 @@ +Główne zmiany w tej wersji: Poprawki błędów! +Pełna lista zmian: https://github.com/vector-im/element-android/releases/tag/v1.3.8 diff --git a/fastlane/metadata/android/pl-PL/changelogs/40103090.txt b/fastlane/metadata/android/pl-PL/changelogs/40103090.txt new file mode 100644 index 0000000000..13e1aed51e --- /dev/null +++ b/fastlane/metadata/android/pl-PL/changelogs/40103090.txt @@ -0,0 +1,2 @@ +Główne zmiany w tej wersji: Dodano obsługę wersji roboczej wiadomości głosowych. Wiele poprawek! +Pełna lista zmian: https://github.com/vector-im/element-android/releases/tag/v1.3.9 diff --git a/fastlane/metadata/android/pl-PL/changelogs/40103100.txt b/fastlane/metadata/android/pl-PL/changelogs/40103100.txt new file mode 100644 index 0000000000..406f07f70f --- /dev/null +++ b/fastlane/metadata/android/pl-PL/changelogs/40103100.txt @@ -0,0 +1,2 @@ +Główne zmiany w tej wersji: Dodano obsługę ankiet (w laboratoriach). Nowy projekt podglądu adresu URL. +Pełna lista zmian: https://github.com/vector-im/element-android/releases/tag/v1.3.10 diff --git a/fastlane/metadata/android/pl-PL/changelogs/40103110.txt b/fastlane/metadata/android/pl-PL/changelogs/40103110.txt new file mode 100644 index 0000000000..f9b38673c7 --- /dev/null +++ b/fastlane/metadata/android/pl-PL/changelogs/40103110.txt @@ -0,0 +1,2 @@ +Główne zmiany w tej wersji: Poprawki błędów! +Pełna lista zmian: https://github.com/vector-im/element-android/releases/tag/v1.3.11 diff --git a/fastlane/metadata/android/pl-PL/changelogs/40103120.txt b/fastlane/metadata/android/pl-PL/changelogs/40103120.txt new file mode 100644 index 0000000000..db6c1c86bd --- /dev/null +++ b/fastlane/metadata/android/pl-PL/changelogs/40103120.txt @@ -0,0 +1,2 @@ +Główne zmiany w tej wersji: Poprawki błędów! +Pełna lista zmian: https://github.com/vector-im/element-android/releases/tag/v1.3.12 diff --git a/fastlane/metadata/android/pl-PL/changelogs/40103130.txt b/fastlane/metadata/android/pl-PL/changelogs/40103130.txt new file mode 100644 index 0000000000..affd085177 --- /dev/null +++ b/fastlane/metadata/android/pl-PL/changelogs/40103130.txt @@ -0,0 +1,2 @@ +Główne zmiany w tej wersji: Pierwsza zmiana na ekranach rejestracji, w tym możliwość włączenia Analytics. Dodano obsługę dla wydarzeń z dodatkiem matematyki w laboratoriach. +Pełna lista zmian: https://github.com/vector-im/element-android/releases/tag/v1.3.13 diff --git a/fastlane/metadata/android/pl-PL/changelogs/40103140.txt b/fastlane/metadata/android/pl-PL/changelogs/40103140.txt new file mode 100644 index 0000000000..0727d3b4ff --- /dev/null +++ b/fastlane/metadata/android/pl-PL/changelogs/40103140.txt @@ -0,0 +1,2 @@ +Główne zmiany w tej wersji: Pierwsza zmiana na ekranach rejestracji, w tym możliwość włączenia Analytics. Dodano obsługę dla wydarzeń z dodatkiem matematyki w laboratoriach. +Pełna lista zmian: https://github.com/vector-im/element-android/releases/tag/v1.3.14 diff --git a/fastlane/metadata/android/pl-PL/changelogs/40103150.txt b/fastlane/metadata/android/pl-PL/changelogs/40103150.txt new file mode 100644 index 0000000000..48d6d0feb3 --- /dev/null +++ b/fastlane/metadata/android/pl-PL/changelogs/40103150.txt @@ -0,0 +1,2 @@ +Główne zmiany w tej wersji: Pierwsza zmiana na ekranach rejestracji, w tym możliwość włączenia Analytics. Dodano obsługę dla wydarzeń z dodatkiem matematyki w laboratoriach. +Pełna lista zmian: https://github.com/vector-im/element-android/releases/tag/v1.3.15 diff --git a/fastlane/metadata/android/pl-PL/changelogs/40103160.txt b/fastlane/metadata/android/pl-PL/changelogs/40103160.txt new file mode 100644 index 0000000000..df66e3042a --- /dev/null +++ b/fastlane/metadata/android/pl-PL/changelogs/40103160.txt @@ -0,0 +1,2 @@ +Główne zmiany w tej wersji: wyślij swoją lokalizację do dowolnego pokoju. Edytuj ankietę. +Pełna lista zmian: https://github.com/vector-im/element-android/releases/tag/v1.3.16 diff --git a/fastlane/metadata/android/pl-PL/changelogs/40103170.txt b/fastlane/metadata/android/pl-PL/changelogs/40103170.txt new file mode 100644 index 0000000000..77d40898a6 --- /dev/null +++ b/fastlane/metadata/android/pl-PL/changelogs/40103170.txt @@ -0,0 +1,2 @@ +Główne zmiany w tej wersji: wyślij swoją lokalizację do dowolnego pokoju. Edytuj ankietę. +Pełna lista zmian: https://github.com/vector-im/element-android/releases/tag/v1.3.17 diff --git a/fastlane/metadata/android/pl-PL/changelogs/40103180.txt b/fastlane/metadata/android/pl-PL/changelogs/40103180.txt new file mode 100644 index 0000000000..c0f671a58e --- /dev/null +++ b/fastlane/metadata/android/pl-PL/changelogs/40103180.txt @@ -0,0 +1,2 @@ +Główne zmiany w tej wersji: wyślij swoją lokalizację do dowolnego pokoju. Edytuj ankietę. +Pełna lista zmian: https://github.com/vector-im/element-android/releases/tag/v1.3.18 diff --git a/fastlane/metadata/android/pl-PL/changelogs/40104000.txt b/fastlane/metadata/android/pl-PL/changelogs/40104000.txt new file mode 100644 index 0000000000..f9f4c80cf9 --- /dev/null +++ b/fastlane/metadata/android/pl-PL/changelogs/40104000.txt @@ -0,0 +1,2 @@ +Główne zmiany w tej wersji: Wstępna implementacja komunikatów wątków. Bąbelki wiadomości. +Pełna lista zmian: https://github.com/vector-im/element-android/releases/tag/v1.4.0 diff --git a/fastlane/metadata/android/pl-PL/changelogs/40104020.txt b/fastlane/metadata/android/pl-PL/changelogs/40104020.txt new file mode 100644 index 0000000000..190427d548 --- /dev/null +++ b/fastlane/metadata/android/pl-PL/changelogs/40104020.txt @@ -0,0 +1,2 @@ +Główne zmiany w tej wersji: dodanie obsługi @room i nieujawnionych ankiet, i wiele innych drobnych zmian. +Pełna lista zmian: https://github.com/vector-im/element-android/releases/tag/v1.4.2 diff --git a/fastlane/metadata/android/pl-PL/changelogs/40104040.txt b/fastlane/metadata/android/pl-PL/changelogs/40104040.txt new file mode 100644 index 0000000000..9894e54c03 --- /dev/null +++ b/fastlane/metadata/android/pl-PL/changelogs/40104040.txt @@ -0,0 +1,2 @@ +Główne zmiany w tej wersji: aktualizacje interfejsu użytkownika wskaźnika pisania. Różne poprawki błędów i ulepszenia stabilności. +Pełna lista zmian: https://github.com/vector-im/element-android/releases/tag/v1.4.4 diff --git a/fastlane/metadata/android/pl-PL/changelogs/40104060.txt b/fastlane/metadata/android/pl-PL/changelogs/40104060.txt new file mode 100644 index 0000000000..216842ada0 --- /dev/null +++ b/fastlane/metadata/android/pl-PL/changelogs/40104060.txt @@ -0,0 +1,2 @@ +Główne zmiany w tej wersji: Oś czasu wątku jest teraz aktywna i szybsza. Różne poprawki błędów i ulepszenia stabilności. +Pełna lista zmian: https://github.com/vector-im/element-android/releases/tag/v1.4.6 diff --git a/fastlane/metadata/android/pl-PL/changelogs/40104070.txt b/fastlane/metadata/android/pl-PL/changelogs/40104070.txt new file mode 100644 index 0000000000..91c13d3d79 --- /dev/null +++ b/fastlane/metadata/android/pl-PL/changelogs/40104070.txt @@ -0,0 +1,2 @@ +Główne zmiany w tej wersji: Różne poprawki błędów i ulepszenia stabilności. +Pełna lista zmian: https://github.com/vector-im/element-android/releases/tag/v1.4.7 diff --git a/fastlane/metadata/android/pl-PL/changelogs/40104080.txt b/fastlane/metadata/android/pl-PL/changelogs/40104080.txt new file mode 100644 index 0000000000..5286f40de5 --- /dev/null +++ b/fastlane/metadata/android/pl-PL/changelogs/40104080.txt @@ -0,0 +1,2 @@ +Główne zmiany w tej wersji: Oś czasu wątku jest teraz aktywna i szybsza. Różne poprawki błędów i ulepszenia stabilności. +Pełna lista zmian: https://github.com/vector-im/element-android/releases diff --git a/fastlane/metadata/android/pt-BR/changelogs/40104040.txt b/fastlane/metadata/android/pt-BR/changelogs/40104040.txt new file mode 100644 index 0000000000..87a61ec102 --- /dev/null +++ b/fastlane/metadata/android/pt-BR/changelogs/40104040.txt @@ -0,0 +1,2 @@ +Principais mudanças nesta versão: atualizações de UI de indicador de digitação. Vários consertos de bugs e melhorias de estabilidade. +Changelog completo: https://github.com/vector-im/element-android/releases/tag/v1.4.4 diff --git a/fastlane/metadata/android/pt-BR/changelogs/40104060.txt b/fastlane/metadata/android/pt-BR/changelogs/40104060.txt new file mode 100644 index 0000000000..17d8225c0d --- /dev/null +++ b/fastlane/metadata/android/pt-BR/changelogs/40104060.txt @@ -0,0 +1,2 @@ +Principais mudanças nesta versão: Timeline de Thread estão agora ao vivo e mais rápidas. Vários consertos de bugs e melhorias de estabilidade. +Changelog completo: https://github.com/vector-im/element-android/releases/tag/v1.4.6 diff --git a/fastlane/metadata/android/pt-BR/changelogs/40104070.txt b/fastlane/metadata/android/pt-BR/changelogs/40104070.txt new file mode 100644 index 0000000000..fad9bfe739 --- /dev/null +++ b/fastlane/metadata/android/pt-BR/changelogs/40104070.txt @@ -0,0 +1,2 @@ +Principais mudanças nesta versão: Vários consertos de bugs e melhorias de estabilidade. +Changelog completo: https://github.com/vector-im/element-android/releases/tag/v1.4.7 diff --git a/fastlane/metadata/android/pt-BR/changelogs/40104080.txt b/fastlane/metadata/android/pt-BR/changelogs/40104080.txt new file mode 100644 index 0000000000..ca2b54f2f2 --- /dev/null +++ b/fastlane/metadata/android/pt-BR/changelogs/40104080.txt @@ -0,0 +1,2 @@ +Principais mudanças nesta versão: Timeline de Thread estão agora ao vivo e mais rápidas. Vários consertos de bugs e melhorias de estabilidade. +Changelog completo: https://github.com/vector-im/element-android/releases diff --git a/fastlane/metadata/android/ru-RU/changelogs/40104000.txt b/fastlane/metadata/android/ru-RU/changelogs/40104000.txt new file mode 100644 index 0000000000..f6bf34b3cc --- /dev/null +++ b/fastlane/metadata/android/ru-RU/changelogs/40104000.txt @@ -0,0 +1,2 @@ +Основные изменения в этой версии: Начальная реализация веток сообщений. Сообщения пузыри. +Полный список изменений: https://github.com/vector-im/element-android/releases/tag/v1.4.0 diff --git a/fastlane/metadata/android/ru-RU/changelogs/40104020.txt b/fastlane/metadata/android/ru-RU/changelogs/40104020.txt new file mode 100644 index 0000000000..864bd03d5e --- /dev/null +++ b/fastlane/metadata/android/ru-RU/changelogs/40104020.txt @@ -0,0 +1,2 @@ +Основные изменения в этой версии: добавлена поддержка @room и нераскрытых опросов, а также множество других мелких изменений. +Полный список изменений: https://github.com/vector-im/element-android/releases/tag/v1.4.2 diff --git a/fastlane/metadata/android/sk/changelogs/40104040.txt b/fastlane/metadata/android/sk/changelogs/40104040.txt new file mode 100644 index 0000000000..67f04df995 --- /dev/null +++ b/fastlane/metadata/android/sk/changelogs/40104040.txt @@ -0,0 +1,2 @@ +Hlavné zmeny v tejto verzii: aktualizácie používateľského rozhrania indikátora písania. Rôzne opravy chýb a vylepšenia stability. +Úplný zoznam zmien: https://github.com/vector-im/element-android/releases/tag/v1.4.4 diff --git a/fastlane/metadata/android/sk/changelogs/40104060.txt b/fastlane/metadata/android/sk/changelogs/40104060.txt new file mode 100644 index 0000000000..53e192fe7c --- /dev/null +++ b/fastlane/metadata/android/sk/changelogs/40104060.txt @@ -0,0 +1,2 @@ +Hlavné zmeny v tejto verzii: Vlákna na časovej osi sú teraz živé a rýchlejšie. Rôzne opravy chýb a vylepšenia stability. +Úplný zoznam zmien: https://github.com/vector-im/element-android/releases/tag/v1.4.6 diff --git a/fastlane/metadata/android/sk/changelogs/40104070.txt b/fastlane/metadata/android/sk/changelogs/40104070.txt new file mode 100644 index 0000000000..1e056cf32f --- /dev/null +++ b/fastlane/metadata/android/sk/changelogs/40104070.txt @@ -0,0 +1,2 @@ +Hlavné zmeny v tejto verzii: Rôzne opravy chýb a vylepšenia stability. +Úplný zoznam zmien: https://github.com/vector-im/element-android/releases/tag/v1.4.7 diff --git a/fastlane/metadata/android/sk/changelogs/40104080.txt b/fastlane/metadata/android/sk/changelogs/40104080.txt new file mode 100644 index 0000000000..7f42250fb9 --- /dev/null +++ b/fastlane/metadata/android/sk/changelogs/40104080.txt @@ -0,0 +1,2 @@ +Hlavné zmeny v tejto verzii: Vlákna na časovej osi sú teraz živé a rýchlejšie. Rôzne opravy chýb a vylepšenia stability. +Úplný zoznam zmien: https://github.com/vector-im/element-android/releases diff --git a/fastlane/metadata/android/sq/changelogs/40104040.txt b/fastlane/metadata/android/sq/changelogs/40104040.txt new file mode 100644 index 0000000000..5cece335e3 --- /dev/null +++ b/fastlane/metadata/android/sq/changelogs/40104040.txt @@ -0,0 +1,2 @@ +Ndryshimet kryesore në këtë version: përditësime UI treguesi shtypjeje. Ndreqje të metash të ndryshme dhe përmirësime qëndrueshmërie. +Regjistër i plotë ndryshimesh: https://github.com/vector-im/element-android/releases/tag/v1.4.4 diff --git a/fastlane/metadata/android/sq/changelogs/40104060.txt b/fastlane/metadata/android/sq/changelogs/40104060.txt new file mode 100644 index 0000000000..48cbd2150c --- /dev/null +++ b/fastlane/metadata/android/sq/changelogs/40104060.txt @@ -0,0 +1,2 @@ +Ndryshimet kryesore në këtë version: Vijat kohore të rrjedhave tani janë në punë dhe më të shpejta. Ndreqje të metash të ndryshme dhe përmirësime qëndrueshmërie. +Regjistër i plotë ndryshimesh: https://github.com/vector-im/element-android/releases/tag/v1.4.6 diff --git a/fastlane/metadata/android/sq/changelogs/40104070.txt b/fastlane/metadata/android/sq/changelogs/40104070.txt new file mode 100644 index 0000000000..ad487c57ee --- /dev/null +++ b/fastlane/metadata/android/sq/changelogs/40104070.txt @@ -0,0 +1,2 @@ +Ndryshimet kryesore në këtë version: Ndreqje të metash të ndryshme dhe përmirësime qëndrueshmërie. +Regjistër i plotë ndryshimesh: https://github.com/vector-im/element-android/releases/tag/v1.4.7 diff --git a/fastlane/metadata/android/sq/changelogs/40104080.txt b/fastlane/metadata/android/sq/changelogs/40104080.txt new file mode 100644 index 0000000000..49156d0c96 --- /dev/null +++ b/fastlane/metadata/android/sq/changelogs/40104080.txt @@ -0,0 +1,2 @@ +Ndryshimet kryesore në këtë version: Vijat kohore të rrjedhave tani janë në punë dhe më të shpejta. Ndreqje të metash të ndryshme dhe përmirësime qëndrueshmërie. +Regjistër i plotë ndryshimesh: https://github.com/vector-im/element-android/releases diff --git a/fastlane/metadata/android/sv-SE/changelogs/40104040.txt b/fastlane/metadata/android/sv-SE/changelogs/40104040.txt new file mode 100644 index 0000000000..0601b26873 --- /dev/null +++ b/fastlane/metadata/android/sv-SE/changelogs/40104040.txt @@ -0,0 +1,2 @@ +Huvudsakliga ändringar i den här versionen: gränssnittsuppdateringar för skrivindikator. Diverse buggfixar och stabilitetsförbättringar. +Full ändringslogg: https://github.com/vector-im/element-android/releases/tag/v1.4.4 diff --git a/fastlane/metadata/android/sv-SE/changelogs/40104060.txt b/fastlane/metadata/android/sv-SE/changelogs/40104060.txt new file mode 100644 index 0000000000..16a49fa3bc --- /dev/null +++ b/fastlane/metadata/android/sv-SE/changelogs/40104060.txt @@ -0,0 +1,2 @@ +Huvudsakliga ändringar i den här versionen: Trådtidslinje är nu live och snabbare. Diverse buggfixar och stabilitetsförbättringar. +Full ändringslogg: https://github.com/vector-im/element-android/releases/tag/v1.4.6 diff --git a/fastlane/metadata/android/sv-SE/changelogs/40104070.txt b/fastlane/metadata/android/sv-SE/changelogs/40104070.txt new file mode 100644 index 0000000000..01f79e85d6 --- /dev/null +++ b/fastlane/metadata/android/sv-SE/changelogs/40104070.txt @@ -0,0 +1,2 @@ +Huvudsakliga ändringar i den här versionen: Diverse buggfixar och stabilitetsförbättringar. +Full ändringslogg: https://github.com/vector-im/element-android/releases/tag/v1.4.7 diff --git a/fastlane/metadata/android/sv-SE/changelogs/40104080.txt b/fastlane/metadata/android/sv-SE/changelogs/40104080.txt new file mode 100644 index 0000000000..2ac7df2cda --- /dev/null +++ b/fastlane/metadata/android/sv-SE/changelogs/40104080.txt @@ -0,0 +1,2 @@ +Huvudsakliga ändringar i den här versionen: Trådtidslinje är nu live och snabbare. Diverse buggfixar och stabilitetsförbättringar. +Full ändringslogg: https://github.com/vector-im/element-android/releases/tag/v1.4.8 diff --git a/fastlane/metadata/android/uk/changelogs/40104040.txt b/fastlane/metadata/android/uk/changelogs/40104040.txt new file mode 100644 index 0000000000..313eb56045 --- /dev/null +++ b/fastlane/metadata/android/uk/changelogs/40104040.txt @@ -0,0 +1,2 @@ +Основні зміни в цій версії: оновлено індикатор набору. Виправлено різні вади й удосконалено стабільність. +Вичерпний журнал змін: https://github.com/vector-im/element-android/releases/tag/v1.4.4 diff --git a/fastlane/metadata/android/uk/changelogs/40104060.txt b/fastlane/metadata/android/uk/changelogs/40104060.txt new file mode 100644 index 0000000000..28f051724a --- /dev/null +++ b/fastlane/metadata/android/uk/changelogs/40104060.txt @@ -0,0 +1,2 @@ +Основні зміни в цій версії: Хронологія тредів працює наживо і швидше. Усунуто різні вади й поліпшено стабільність. +Вичерпний перелік змін: https://github.com/vector-im/element-android/releases/tag/v1.4.6 diff --git a/fastlane/metadata/android/uk/changelogs/40104070.txt b/fastlane/metadata/android/uk/changelogs/40104070.txt new file mode 100644 index 0000000000..dff28b7825 --- /dev/null +++ b/fastlane/metadata/android/uk/changelogs/40104070.txt @@ -0,0 +1,2 @@ +Основні зміни в цій версії: Усунуто різні вади й поліпшено стабільність. +Вичерпний перелік змін: https://github.com/vector-im/element-android/releases/tag/v1.4.7 diff --git a/fastlane/metadata/android/uk/changelogs/40104080.txt b/fastlane/metadata/android/uk/changelogs/40104080.txt new file mode 100644 index 0000000000..e5f7e7b271 --- /dev/null +++ b/fastlane/metadata/android/uk/changelogs/40104080.txt @@ -0,0 +1,2 @@ +Основні зміни в цій версії: Хронологія тредів працює наживо і швидше. Усунуто різні вади й поліпшено стабільність. +Вичерпний перелік змін: https://github.com/vector-im/element-android/releases diff --git a/fastlane/metadata/android/zh-TW/changelogs/40104040.txt b/fastlane/metadata/android/zh-TW/changelogs/40104040.txt new file mode 100644 index 0000000000..8949ec3486 --- /dev/null +++ b/fastlane/metadata/android/zh-TW/changelogs/40104040.txt @@ -0,0 +1,2 @@ +此版本中的主要變動:輸入指示器使用者介面更新。許多臭蟲修復與穩定性改善。 +完整的變更紀錄:https://github.com/vector-im/element-android/releases/tag/v1.4.4 diff --git a/fastlane/metadata/android/zh-TW/changelogs/40104060.txt b/fastlane/metadata/android/zh-TW/changelogs/40104060.txt new file mode 100644 index 0000000000..316fad3363 --- /dev/null +++ b/fastlane/metadata/android/zh-TW/changelogs/40104060.txt @@ -0,0 +1,2 @@ +此版本中的主要變動:討論串時間軸現已更新,而且更快了。多個臭蟲修復與穩定性改善。 +完整的變更紀錄:https://github.com/vector-im/element-android/releases/tag/v1.4.6 diff --git a/fastlane/metadata/android/zh-TW/changelogs/40104070.txt b/fastlane/metadata/android/zh-TW/changelogs/40104070.txt new file mode 100644 index 0000000000..2cd9da666e --- /dev/null +++ b/fastlane/metadata/android/zh-TW/changelogs/40104070.txt @@ -0,0 +1,2 @@ +此版本中的主要變動:多個臭蟲修復與穩定性改善。 +完整的變更紀錄:https://github.com/vector-im/element-android/releases/tag/v1.4.7 diff --git a/fastlane/metadata/android/zh-TW/changelogs/40104080.txt b/fastlane/metadata/android/zh-TW/changelogs/40104080.txt new file mode 100644 index 0000000000..c036aa7d56 --- /dev/null +++ b/fastlane/metadata/android/zh-TW/changelogs/40104080.txt @@ -0,0 +1,2 @@ +此版本中的主要變動:討論串時間軸現已更新,而且更快了。多個臭蟲修復與穩定性改善。 +完整的變更紀錄:https://github.com/vector-im/element-android/releases diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index db3bccc1f9..e1e0c8dc42 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionSha256Sum=a9a7b7baba105f6557c9dcf9c3c6e8f7e57e6b49889c5f1d133f015d0727e4be -distributionUrl=https\://services.gradle.org/distributions/gradle-7.4.1-all.zip +distributionSha256Sum=e6d864e3b5bc05cc62041842b306383fc1fefcec359e70cebb1d470a6094ca82 +distributionUrl=https\://services.gradle.org/distributions/gradle-7.4.2-all.zip zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists diff --git a/library/attachment-viewer/src/main/java/im/vector/lib/attachmentviewer/ImageLoaderTarget.kt b/library/attachment-viewer/src/main/java/im/vector/lib/attachmentviewer/ImageLoaderTarget.kt index 99686eaabb..7b54438a52 100644 --- a/library/attachment-viewer/src/main/java/im/vector/lib/attachmentviewer/ImageLoaderTarget.kt +++ b/library/attachment-viewer/src/main/java/im/vector/lib/attachmentviewer/ImageLoaderTarget.kt @@ -37,7 +37,7 @@ interface ImageLoaderTarget { } internal class DefaultImageLoaderTarget(val holder: AnimatedImageViewHolder, private val contextView: ImageView) : - ImageLoaderTarget { + ImageLoaderTarget { override fun contextView(): ImageView { return contextView } diff --git a/library/core-utils/src/main/java/im/vector/lib/core/utils/flow/TimingOperators.kt b/library/core-utils/src/main/java/im/vector/lib/core/utils/flow/TimingOperators.kt index 065c19c17a..2efb439ace 100644 --- a/library/core-utils/src/main/java/im/vector/lib/core/utils/flow/TimingOperators.kt +++ b/library/core-utils/src/main/java/im/vector/lib/core/utils/flow/TimingOperators.kt @@ -25,7 +25,6 @@ import kotlinx.coroutines.channels.produce import kotlinx.coroutines.coroutineScope import kotlinx.coroutines.delay import kotlinx.coroutines.flow.Flow -import kotlinx.coroutines.flow.collect import kotlinx.coroutines.flow.consumeAsFlow import kotlinx.coroutines.flow.flow import kotlinx.coroutines.isActive diff --git a/library/diff-match-patch/src/main/java/name/fraser/neil/plaintext/diff_match_patch.java b/library/diff-match-patch/src/main/java/name/fraser/neil/plaintext/diff_match_patch.java index 9d07867de5..484fb38b48 100644 --- a/library/diff-match-patch/src/main/java/name/fraser/neil/plaintext/diff_match_patch.java +++ b/library/diff-match-patch/src/main/java/name/fraser/neil/plaintext/diff_match_patch.java @@ -21,7 +21,15 @@ package name.fraser.neil.plaintext; import java.io.UnsupportedEncodingException; import java.net.URLDecoder; import java.net.URLEncoder; -import java.util.*; +import java.util.ArrayDeque; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Deque; +import java.util.HashMap; +import java.util.LinkedList; +import java.util.List; +import java.util.ListIterator; +import java.util.Map; import java.util.regex.Matcher; import java.util.regex.Pattern; diff --git a/library/jsonviewer/build.gradle b/library/jsonviewer/build.gradle index 15f46754b3..0cad8ac171 100644 --- a/library/jsonviewer/build.gradle +++ b/library/jsonviewer/build.gradle @@ -59,7 +59,7 @@ dependencies { implementation libs.jetbrains.coroutinesCore implementation libs.jetbrains.coroutinesAndroid - testImplementation 'org.json:json:20211205' + testImplementation 'org.json:json:20220320' testImplementation libs.tests.junit androidTestImplementation libs.androidx.junit androidTestImplementation libs.androidx.espressoCore diff --git a/library/jsonviewer/src/main/java/org/billcarsonfr/jsonviewer/JSonViewerDialog.kt b/library/jsonviewer/src/main/java/org/billcarsonfr/jsonviewer/JSonViewerDialog.kt index a8d9cac849..0ebf539d4d 100644 --- a/library/jsonviewer/src/main/java/org/billcarsonfr/jsonviewer/JSonViewerDialog.kt +++ b/library/jsonviewer/src/main/java/org/billcarsonfr/jsonviewer/JSonViewerDialog.kt @@ -27,9 +27,9 @@ import com.airbnb.mvrx.Mavericks class JSonViewerDialog : DialogFragment() { override fun onCreateView( - inflater: LayoutInflater, - container: ViewGroup?, - savedInstanceState: Bundle? + inflater: LayoutInflater, + container: ViewGroup?, + savedInstanceState: Bundle? ): View? { return inflater.inflate(R.layout.fragment_dialog_jv, container, false) } @@ -39,15 +39,15 @@ class JSonViewerDialog : DialogFragment() { val args: JSonViewerFragmentArgs = arguments?.getParcelable(Mavericks.KEY_ARG) ?: return if (savedInstanceState == null) { childFragmentManager.beginTransaction() - .replace( - R.id.fragmentContainer, JSonViewerFragment.newInstance( - args.jsonString, - args.defaultOpenDepth, - true, - args.styleProvider + .replace( + R.id.fragmentContainer, JSonViewerFragment.newInstance( + args.jsonString, + args.defaultOpenDepth, + true, + args.styleProvider ) - ) - .commitNow() + ) + .commitNow() } } @@ -63,13 +63,13 @@ class JSonViewerDialog : DialogFragment() { companion object { fun newInstance( - jsonString: String, - initialOpenDepth: Int = -1, - styleProvider: JSonViewerStyleProvider? = null + jsonString: String, + initialOpenDepth: Int = -1, + styleProvider: JSonViewerStyleProvider? = null ): JSonViewerDialog { val args = Bundle() val parcelableArgs = - JSonViewerFragmentArgs(jsonString, initialOpenDepth, false, styleProvider) + JSonViewerFragmentArgs(jsonString, initialOpenDepth, false, styleProvider) args.putParcelable(Mavericks.KEY_ARG, parcelableArgs) return JSonViewerDialog().apply { arguments = args } } diff --git a/library/jsonviewer/src/main/java/org/billcarsonfr/jsonviewer/JSonViewerEpoxyController.kt b/library/jsonviewer/src/main/java/org/billcarsonfr/jsonviewer/JSonViewerEpoxyController.kt index 96b5a9c997..9f8093f801 100644 --- a/library/jsonviewer/src/main/java/org/billcarsonfr/jsonviewer/JSonViewerEpoxyController.kt +++ b/library/jsonviewer/src/main/java/org/billcarsonfr/jsonviewer/JSonViewerEpoxyController.kt @@ -20,13 +20,12 @@ import android.content.Context import android.view.View import com.airbnb.epoxy.TypedEpoxyController import com.airbnb.mvrx.Fail -import com.airbnb.mvrx.Success import im.vector.lib.core.utils.epoxy.charsequence.toEpoxyCharSequence import me.gujun.android.span.Span import me.gujun.android.span.span internal class JSonViewerEpoxyController(private val context: Context) : - TypedEpoxyController() { + TypedEpoxyController() { private var styleProvider: JSonViewerStyleProvider = JSonViewerStyleProvider.default(context) @@ -44,10 +43,8 @@ internal class JSonViewerEpoxyController(private val context: Context) : text(async.error.localizedMessage?.toEpoxyCharSequence()) } } - is Success -> { - val model = data.root.invoke() - - model?.let { + else -> { + async.invoke()?.let { buildRec(it, 0, "") } } @@ -55,9 +52,9 @@ internal class JSonViewerEpoxyController(private val context: Context) : } private fun buildRec( - model: JSonViewerModel, - depth: Int, - idBase: String + model: JSonViewerModel, + depth: Int, + idBase: String ) { val host = this val id = "$idBase/${model.key ?: model.index}_${model.isExpanded}}" @@ -74,34 +71,34 @@ internal class JSonViewerEpoxyController(private val context: Context) : id(id + "_sum") depth(depth) text( - span { - if (model.key != null) { - span("\"${model.key}\"") { - textColor = host.styleProvider.keyColor - } - span(" : ") { - textColor = host.styleProvider.baseColor - } - } - if (model.index != null) { - span("${model.index}") { - textColor = host.styleProvider.secondaryColor - } - span(" : ") { - textColor = host.styleProvider.baseColor - } - } span { - +"{+${model.keys.size}}" - textColor = host.styleProvider.baseColor - } - }.toEpoxyCharSequence() + if (model.key != null) { + span("\"${model.key}\"") { + textColor = host.styleProvider.keyColor + } + span(" : ") { + textColor = host.styleProvider.baseColor + } + } + if (model.index != null) { + span("${model.index}") { + textColor = host.styleProvider.secondaryColor + } + span(" : ") { + textColor = host.styleProvider.baseColor + } + } + span { + +"{+${model.keys.size}}" + textColor = host.styleProvider.baseColor + } + }.toEpoxyCharSequence() ) itemClickListener(View.OnClickListener { host.itemClicked(model) }) } } } - is JSonViewerArray -> { + is JSonViewerArray -> { if (model.isExpanded) { open(id, model.key, model.index, depth, false, model) model.items.forEach { @@ -113,6 +110,38 @@ internal class JSonViewerEpoxyController(private val context: Context) : id(id + "_sum") depth(depth) text( + span { + if (model.key != null) { + span("\"${model.key}\"") { + textColor = host.styleProvider.keyColor + } + span(" : ") { + textColor = host.styleProvider.baseColor + } + } + if (model.index != null) { + span("${model.index}") { + textColor = host.styleProvider.secondaryColor + } + span(" : ") { + textColor = host.styleProvider.baseColor + } + } + span { + +"[+${model.items.size}]" + textColor = host.styleProvider.baseColor + } + }.toEpoxyCharSequence() + ) + itemClickListener(View.OnClickListener { host.itemClicked(model) }) + } + } + } + is JSonViewerLeaf -> { + valueItem { + id(id) + depth(depth) + text( span { if (model.key != null) { span("\"${model.key}\"") { @@ -122,6 +151,7 @@ internal class JSonViewerEpoxyController(private val context: Context) : textColor = host.styleProvider.baseColor } } + if (model.index != null) { span("${model.index}") { textColor = host.styleProvider.secondaryColor @@ -130,41 +160,8 @@ internal class JSonViewerEpoxyController(private val context: Context) : textColor = host.styleProvider.baseColor } } - span { - +"[+${model.items.size}]" - textColor = host.styleProvider.baseColor - } + append(host.valueToSpan(model)) }.toEpoxyCharSequence() - ) - itemClickListener(View.OnClickListener { host.itemClicked(model) }) - } - } - } - is JSonViewerLeaf -> { - valueItem { - id(id) - depth(depth) - text( - span { - if (model.key != null) { - span("\"${model.key}\"") { - textColor = host.styleProvider.keyColor - } - span(" : ") { - textColor = host.styleProvider.baseColor - } - } - - if (model.index != null) { - span("${model.index}") { - textColor = host.styleProvider.secondaryColor - } - span(" : ") { - textColor = host.styleProvider.baseColor - } - } - append(host.valueToSpan(model)) - }.toEpoxyCharSequence() ) copyValue(model.stringRes) } @@ -175,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 } @@ -190,7 +187,7 @@ internal class JSonViewerEpoxyController(private val context: Context) : textColor = host.styleProvider.booleanColor } } - JSONType.NULL -> { + JSONType.NULL -> { span("null") { textColor = host.styleProvider.booleanColor } @@ -199,42 +196,42 @@ internal class JSonViewerEpoxyController(private val context: Context) : } private fun open( - id: String, - key: String?, - index: Int?, - depth: Int, - isObject: Boolean = true, - composed: JSonViewerModel + id: String, + key: String?, + index: Int?, + depth: Int, + isObject: Boolean = true, + composed: JSonViewerModel ) { val host = this valueItem { id("${id}_Open") depth(depth) text( - span { - if (key != null) { - span("\"$key\"") { - textColor = host.styleProvider.keyColor + span { + if (key != null) { + span("\"$key\"") { + textColor = host.styleProvider.keyColor + } + span(" : ") { + textColor = host.styleProvider.baseColor + } } - span(" : ") { - textColor = host.styleProvider.baseColor + if (index != null) { + span("$index") { + textColor = host.styleProvider.secondaryColor + } + span(" : ") { + textColor = host.styleProvider.baseColor + } } - } - if (index != null) { - span("$index") { + span("- ") { textColor = host.styleProvider.secondaryColor } - span(" : ") { + span("{".takeIf { isObject } ?: "[") { textColor = host.styleProvider.baseColor } - } - span("- ") { - textColor = host.styleProvider.secondaryColor - } - span("{".takeIf { isObject } ?: "[") { - textColor = host.styleProvider.baseColor - } - }.toEpoxyCharSequence() + }.toEpoxyCharSequence() ) itemClickListener(View.OnClickListener { host.itemClicked(composed) }) } @@ -251,10 +248,10 @@ internal class JSonViewerEpoxyController(private val context: Context) : id("${id}_Close") depth(depth) text( - span { - text = "}".takeIf { isObject } ?: "]" - textColor = host.styleProvider.baseColor - }.toEpoxyCharSequence() + span { + text = "}".takeIf { isObject } ?: "]" + textColor = host.styleProvider.baseColor + }.toEpoxyCharSequence() ) } } diff --git a/library/jsonviewer/src/main/java/org/billcarsonfr/jsonviewer/JSonViewerFragment.kt b/library/jsonviewer/src/main/java/org/billcarsonfr/jsonviewer/JSonViewerFragment.kt index 51e2797958..fbf6f88bc3 100644 --- a/library/jsonviewer/src/main/java/org/billcarsonfr/jsonviewer/JSonViewerFragment.kt +++ b/library/jsonviewer/src/main/java/org/billcarsonfr/jsonviewer/JSonViewerFragment.kt @@ -32,10 +32,10 @@ import kotlinx.parcelize.Parcelize @Parcelize internal data class JSonViewerFragmentArgs( - val jsonString: String, - val defaultOpenDepth: Int, - val wrap: Boolean, - val styleProvider: JSonViewerStyleProvider? + val jsonString: String, + val defaultOpenDepth: Int, + val wrap: Boolean, + val styleProvider: JSonViewerStyleProvider? ) : Parcelable class JSonViewerFragment : Fragment(), MavericksView { @@ -49,20 +49,20 @@ class JSonViewerFragment : Fragment(), MavericksView { private lateinit var recyclerView: EpoxyRecyclerView override fun onCreateView( - inflater: LayoutInflater, - container: ViewGroup?, - savedInstanceState: Bundle? + inflater: LayoutInflater, + container: ViewGroup?, + savedInstanceState: Bundle? ): View? { val args: JSonViewerFragmentArgs? = arguments?.getParcelable(Mavericks.KEY_ARG) val inflate = - if (args?.wrap == true) { - inflater.inflate(R.layout.fragment_jv_recycler_view_wrap, container, false) - } else { - inflater.inflate(R.layout.fragment_jv_recycler_view, container, false) - } + if (args?.wrap == true) { + inflater.inflate(R.layout.fragment_jv_recycler_view_wrap, container, false) + } else { + inflater.inflate(R.layout.fragment_jv_recycler_view, container, false) + } recyclerView = inflate.findViewById(R.id.jvRecyclerView) recyclerView.layoutManager = - LinearLayoutManager(context, LinearLayoutManager.VERTICAL, false) + LinearLayoutManager(context, LinearLayoutManager.VERTICAL, false) recyclerView.setController(epoxyController) epoxyController.setStyle(args?.styleProvider) registerForContextMenu(recyclerView) @@ -79,21 +79,21 @@ class JSonViewerFragment : Fragment(), MavericksView { companion object { fun newInstance( - jsonString: String, - initialOpenDepth: Int = -1, - wrap: Boolean = false, - styleProvider: JSonViewerStyleProvider? = null + jsonString: String, + initialOpenDepth: Int = -1, + wrap: Boolean = false, + styleProvider: JSonViewerStyleProvider? = null ): JSonViewerFragment { return JSonViewerFragment().apply { arguments = Bundle().apply { putParcelable( - Mavericks.KEY_ARG, - JSonViewerFragmentArgs( - jsonString, - initialOpenDepth, - wrap, - styleProvider - ) + Mavericks.KEY_ARG, + JSonViewerFragmentArgs( + jsonString, + initialOpenDepth, + wrap, + styleProvider + ) ) } } diff --git a/library/jsonviewer/src/main/java/org/billcarsonfr/jsonviewer/JSonViewerModel.kt b/library/jsonviewer/src/main/java/org/billcarsonfr/jsonviewer/JSonViewerModel.kt index 3d1f8dd3e2..6940e79e3f 100644 --- a/library/jsonviewer/src/main/java/org/billcarsonfr/jsonviewer/JSonViewerModel.kt +++ b/library/jsonviewer/src/main/java/org/billcarsonfr/jsonviewer/JSonViewerModel.kt @@ -30,8 +30,8 @@ internal interface Composed { } internal class JSonViewerObject(key: String?, index: Int?, jObject: JSONObject) : - JSonViewerModel(key, index, jObject), - Composed { + JSonViewerModel(key, index, jObject), + Composed { var keys = LinkedHashMap() @@ -41,7 +41,7 @@ internal class JSonViewerObject(key: String?, index: Int?, jObject: JSONObject) } internal class JSonViewerArray(key: String?, index: Int?, jObject: JSONArray) : - JSonViewerModel(key, index, jObject), Composed { + JSonViewerModel(key, index, jObject), Composed { var items = ArrayList() override fun addChild(model: JSonViewerModel) { @@ -50,7 +50,7 @@ internal class JSonViewerArray(key: String?, index: Int?, jObject: JSONArray) : } internal class JSonViewerLeaf(key: String?, index: Int?, val stringRes: String, val type: JSONType) : - JSonViewerModel(key, index, stringRes) + JSonViewerModel(key, index, stringRes) internal enum class JSONType { STRING, @@ -75,41 +75,41 @@ internal object ModelParser { when (obj) { is JSONObject -> { val objectComposed = JSonViewerObject(key, index, obj) - .apply { isExpanded = initialOpenDepth == -1 || depth <= initialOpenDepth } + .apply { isExpanded = initialOpenDepth == -1 || depth <= initialOpenDepth } objectComposed.depth = depth obj.keys().forEach { eval(objectComposed, it, null, obj.get(it), depth + 1, initialOpenDepth) } parent.addChild(objectComposed) } - is JSONArray -> { + is JSONArray -> { val objectComposed = JSonViewerArray(key, index, obj) - .apply { isExpanded = initialOpenDepth == -1 || depth <= initialOpenDepth } + .apply { isExpanded = initialOpenDepth == -1 || depth <= initialOpenDepth } objectComposed.depth = depth for (i in 0 until obj.length()) { eval(objectComposed, null, i, obj[i], depth + 1, initialOpenDepth) } parent.addChild(objectComposed) } - is String -> { - JSonViewerLeaf(key, index, obj, JSONType.STRING).let { + is String -> { + JSonViewerLeaf(key, index, obj, JSONType.STRING).let { it.depth = depth parent.addChild(it) } } - is Number -> { + is Number -> { JSonViewerLeaf(key, index, obj.toString(), JSONType.NUMBER).let { it.depth = depth parent.addChild(it) } } - is Boolean -> { + is Boolean -> { JSonViewerLeaf(key, index, obj.toString(), JSONType.BOOLEAN).let { it.depth = depth parent.addChild(it) } } - else -> { + else -> { if (obj == JSONObject.NULL) { JSonViewerLeaf(key, index, "null", JSONType.NULL).let { it.depth = depth diff --git a/library/jsonviewer/src/main/java/org/billcarsonfr/jsonviewer/JSonViewerStyleProvider.kt b/library/jsonviewer/src/main/java/org/billcarsonfr/jsonviewer/JSonViewerStyleProvider.kt index 4fc04c91e4..17d8034f2d 100644 --- a/library/jsonviewer/src/main/java/org/billcarsonfr/jsonviewer/JSonViewerStyleProvider.kt +++ b/library/jsonviewer/src/main/java/org/billcarsonfr/jsonviewer/JSonViewerStyleProvider.kt @@ -24,22 +24,22 @@ import kotlinx.parcelize.Parcelize @Parcelize data class JSonViewerStyleProvider( - @ColorInt val keyColor: Int, - @ColorInt val stringColor: Int, - @ColorInt val booleanColor: Int, - @ColorInt val numberColor: Int, - @ColorInt val baseColor: Int, - @ColorInt val secondaryColor: Int + @ColorInt val keyColor: Int, + @ColorInt val stringColor: Int, + @ColorInt val booleanColor: Int, + @ColorInt val numberColor: Int, + @ColorInt val baseColor: Int, + @ColorInt val secondaryColor: Int ) : Parcelable { companion object { fun default(context: Context) = JSonViewerStyleProvider( - keyColor = ContextCompat.getColor(context, R.color.key_color), - stringColor = ContextCompat.getColor(context, R.color.string_color), - booleanColor = ContextCompat.getColor(context, R.color.bool_color), - numberColor = ContextCompat.getColor(context, R.color.number_color), - baseColor = ContextCompat.getColor(context, R.color.base_color), - secondaryColor = ContextCompat.getColor(context, R.color.secondary_color) + keyColor = ContextCompat.getColor(context, R.color.key_color), + stringColor = ContextCompat.getColor(context, R.color.string_color), + booleanColor = ContextCompat.getColor(context, R.color.bool_color), + numberColor = ContextCompat.getColor(context, R.color.number_color), + baseColor = ContextCompat.getColor(context, R.color.base_color), + secondaryColor = ContextCompat.getColor(context, R.color.secondary_color) ) } } diff --git a/library/jsonviewer/src/main/java/org/billcarsonfr/jsonviewer/JSonViewerViewModel.kt b/library/jsonviewer/src/main/java/org/billcarsonfr/jsonviewer/JSonViewerViewModel.kt index bc3f022cfa..d4e8f42604 100644 --- a/library/jsonviewer/src/main/java/org/billcarsonfr/jsonviewer/JSonViewerViewModel.kt +++ b/library/jsonviewer/src/main/java/org/billcarsonfr/jsonviewer/JSonViewerViewModel.kt @@ -28,11 +28,11 @@ import com.airbnb.mvrx.ViewModelContext import kotlinx.coroutines.launch internal data class JSonViewerState( - val root: Async = Uninitialized + val root: Async = Uninitialized ) : MavericksState internal class JSonViewerViewModel(initialState: JSonViewerState) : - MavericksViewModel(initialState) { + MavericksViewModel(initialState) { fun setJsonSource(json: String, initialOpenDepth: Int) { setState { @@ -43,14 +43,14 @@ internal class JSonViewerViewModel(initialState: JSonViewerState) : ModelParser.fromJsonString(json, initialOpenDepth).let { setState { copy( - root = Success(it) + root = Success(it) ) } } } catch (error: Throwable) { setState { copy( - root = Fail(error) + root = Fail(error) ) } } @@ -64,7 +64,7 @@ internal class JSonViewerViewModel(initialState: JSonViewerState) : val arg: JSonViewerFragmentArgs = viewModelContext.args() return try { JSonViewerState( - Success(ModelParser.fromJsonString(arg.jsonString, arg.defaultOpenDepth)) + Success(ModelParser.fromJsonString(arg.jsonString, arg.defaultOpenDepth)) ) } catch (failure: Throwable) { JSonViewerState(Fail(failure)) diff --git a/library/jsonviewer/src/main/java/org/billcarsonfr/jsonviewer/Utils.kt b/library/jsonviewer/src/main/java/org/billcarsonfr/jsonviewer/Utils.kt index efb2bfd855..0ac1cfe5f6 100644 --- a/library/jsonviewer/src/main/java/org/billcarsonfr/jsonviewer/Utils.kt +++ b/library/jsonviewer/src/main/java/org/billcarsonfr/jsonviewer/Utils.kt @@ -22,9 +22,9 @@ import android.util.TypedValue internal object Utils { fun dpToPx(dp: Int, context: Context): Int { return TypedValue.applyDimension( - TypedValue.COMPLEX_UNIT_DIP, - dp.toFloat(), - context.resources.displayMetrics + TypedValue.COMPLEX_UNIT_DIP, + dp.toFloat(), + context.resources.displayMetrics ).toInt() } } diff --git a/library/jsonviewer/src/main/java/org/billcarsonfr/jsonviewer/ValueItem.kt b/library/jsonviewer/src/main/java/org/billcarsonfr/jsonviewer/ValueItem.kt index 00d66645e6..fac7099b37 100644 --- a/library/jsonviewer/src/main/java/org/billcarsonfr/jsonviewer/ValueItem.kt +++ b/library/jsonviewer/src/main/java/org/billcarsonfr/jsonviewer/ValueItem.kt @@ -71,14 +71,14 @@ internal abstract class ValueItem : EpoxyModelWithHolder() { } override fun onCreateContextMenu( - menu: ContextMenu?, - v: View?, - menuInfo: ContextMenu.ContextMenuInfo? + menu: ContextMenu?, + v: View?, + menuInfo: ContextMenu.ContextMenuInfo? ) { if (copyValue != null) { val menuItem = menu?.add(R.string.copy_value) val clipService = - v?.context?.getSystemService(Context.CLIPBOARD_SERVICE) as? ClipboardManager + v?.context?.getSystemService(Context.CLIPBOARD_SERVICE) as? ClipboardManager menuItem?.setOnMenuItemClickListener { clipService?.setPrimaryClip(ClipData.newPlainText("", copyValue)) true diff --git a/library/multipicker/src/main/java/im/vector/lib/multipicker/CameraPicker.kt b/library/multipicker/src/main/java/im/vector/lib/multipicker/CameraPicker.kt index b1442a56e1..785b9fae43 100644 --- a/library/multipicker/src/main/java/im/vector/lib/multipicker/CameraPicker.kt +++ b/library/multipicker/src/main/java/im/vector/lib/multipicker/CameraPicker.kt @@ -23,11 +23,9 @@ import android.provider.MediaStore import androidx.activity.result.ActivityResultLauncher import androidx.core.content.FileProvider import im.vector.lib.multipicker.entity.MultiPickerImageType +import im.vector.lib.multipicker.utils.MediaType +import im.vector.lib.multipicker.utils.createTemporaryMediaFile import im.vector.lib.multipicker.utils.toMultiPickerImageType -import java.io.File -import java.text.SimpleDateFormat -import java.util.Date -import java.util.Locale /** * Implementation of taking a photo with Camera @@ -38,7 +36,7 @@ class CameraPicker { * Start camera by using a ActivityResultLauncher * @return Uri of taken photo or null if the operation is cancelled. */ - fun startWithExpectingFile(context: Context, activityResultLauncher: ActivityResultLauncher): Uri? { + fun startWithExpectingFile(context: Context, activityResultLauncher: ActivityResultLauncher): Uri { val photoUri = createPhotoUri(context) val intent = createIntent().apply { putExtra(MediaStore.EXTRA_OUTPUT, photoUri) @@ -63,19 +61,9 @@ class CameraPicker { companion object { fun createPhotoUri(context: Context): Uri { - val file = createImageFile(context) + val file = createTemporaryMediaFile(context, MediaType.IMAGE) val authority = context.packageName + ".multipicker.fileprovider" return FileProvider.getUriForFile(context, authority, file) } - - private fun createImageFile(context: Context): File { - val timeStamp: String = SimpleDateFormat("yyyyMMdd_HHmmss", Locale.getDefault()).format(Date()) - val storageDir: File = context.filesDir - return File.createTempFile( - "${timeStamp}_", /* prefix */ - ".jpg", /* suffix */ - storageDir /* directory */ - ) - } } } diff --git a/library/multipicker/src/main/java/im/vector/lib/multipicker/CameraVideoPicker.kt b/library/multipicker/src/main/java/im/vector/lib/multipicker/CameraVideoPicker.kt index 76342b6e2e..59601b30d9 100644 --- a/library/multipicker/src/main/java/im/vector/lib/multipicker/CameraVideoPicker.kt +++ b/library/multipicker/src/main/java/im/vector/lib/multipicker/CameraVideoPicker.kt @@ -23,11 +23,9 @@ import android.provider.MediaStore import androidx.activity.result.ActivityResultLauncher import androidx.core.content.FileProvider import im.vector.lib.multipicker.entity.MultiPickerVideoType +import im.vector.lib.multipicker.utils.MediaType +import im.vector.lib.multipicker.utils.createTemporaryMediaFile import im.vector.lib.multipicker.utils.toMultiPickerVideoType -import java.io.File -import java.text.SimpleDateFormat -import java.util.Date -import java.util.Locale /** * Implementation of taking a video with Camera @@ -38,7 +36,7 @@ class CameraVideoPicker { * Start camera by using a ActivityResultLauncher * @return Uri of taken photo or null if the operation is cancelled. */ - fun startWithExpectingFile(context: Context, activityResultLauncher: ActivityResultLauncher): Uri? { + fun startWithExpectingFile(context: Context, activityResultLauncher: ActivityResultLauncher): Uri { val videoUri = createVideoUri(context) val intent = createIntent().apply { putExtra(MediaStore.EXTRA_OUTPUT, videoUri) @@ -63,19 +61,9 @@ class CameraVideoPicker { companion object { fun createVideoUri(context: Context): Uri { - val file = createVideoFile(context) + val file = createTemporaryMediaFile(context, MediaType.VIDEO) val authority = context.packageName + ".multipicker.fileprovider" return FileProvider.getUriForFile(context, authority, file) } - - private fun createVideoFile(context: Context): File { - val timeStamp: String = SimpleDateFormat("yyyyMMdd_HHmmss", Locale.getDefault()).format(Date()) - val storageDir: File = context.filesDir - return File.createTempFile( - "${timeStamp}_", /* prefix */ - ".mp4", /* suffix */ - storageDir /* directory */ - ) - } } } diff --git a/library/multipicker/src/main/java/im/vector/lib/multipicker/MultiPicker.kt b/library/multipicker/src/main/java/im/vector/lib/multipicker/MultiPicker.kt index 6ce50f622a..821c2f0d4c 100644 --- a/library/multipicker/src/main/java/im/vector/lib/multipicker/MultiPicker.kt +++ b/library/multipicker/src/main/java/im/vector/lib/multipicker/MultiPicker.kt @@ -31,15 +31,15 @@ class MultiPicker { @Suppress("UNCHECKED_CAST") fun get(type: MultiPicker): T { return when (type) { - IMAGE -> ImagePicker() as T - VIDEO -> VideoPicker() as T - MEDIA -> MediaPicker() as T - FILE -> FilePicker() as T - AUDIO -> AudioPicker() as T - CONTACT -> ContactPicker() as T - CAMERA -> CameraPicker() as T - CAMERA_VIDEO -> CameraVideoPicker() as T - else -> throw IllegalArgumentException("Unsupported type $type") + IMAGE -> ImagePicker() as T + VIDEO -> VideoPicker() as T + MEDIA -> MediaPicker() as T + FILE -> FilePicker() as T + AUDIO -> AudioPicker() as T + CONTACT -> ContactPicker() as T + CAMERA -> CameraPicker() as T + CAMERA_VIDEO -> CameraVideoPicker() as T + else -> throw IllegalArgumentException("Unsupported type $type") } } } diff --git a/library/multipicker/src/main/java/im/vector/lib/multipicker/utils/MediaFileUtils.kt b/library/multipicker/src/main/java/im/vector/lib/multipicker/utils/MediaFileUtils.kt new file mode 100644 index 0000000000..a029d5e6b1 --- /dev/null +++ b/library/multipicker/src/main/java/im/vector/lib/multipicker/utils/MediaFileUtils.kt @@ -0,0 +1,42 @@ +/* + * Copyright (c) 2022 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package im.vector.lib.multipicker.utils + +import android.content.Context +import java.io.File +import java.text.SimpleDateFormat +import java.util.Date +import java.util.Locale + +internal fun createTemporaryMediaFile(context: Context, mediaType: MediaType): File { + val timeStamp: String = SimpleDateFormat("yyyyMMdd_HHmmss", Locale.getDefault()).format(Date()) + val storageDir: File = context.filesDir.also { it.mkdirs() } + val fileSuffix = when (mediaType) { + MediaType.IMAGE -> ".jpg" + MediaType.VIDEO -> ".mp4" + } + + return File.createTempFile( + "${timeStamp}_", + fileSuffix, + storageDir + ) +} + +internal enum class MediaType { + IMAGE, VIDEO +} diff --git a/library/ui-styles/build.gradle b/library/ui-styles/build.gradle index cee58414c7..0ac513b252 100644 --- a/library/ui-styles/build.gradle +++ b/library/ui-styles/build.gradle @@ -60,6 +60,4 @@ dependencies { implementation 'com.github.vector-im:PFLockScreen-Android:1.0.0-beta12' // dialpad dimen implementation 'im.dlg:android-dialer:1.2.5' - // AudioRecordView attr - implementation 'com.github.Armen101:AudioRecordView:1.0.5' } \ No newline at end of file diff --git a/library/ui-styles/src/main/res/color/checkbox_tint_selector.xml b/library/ui-styles/src/main/res/color/checkbox_tint_selector.xml new file mode 100644 index 0000000000..67e2e68570 --- /dev/null +++ b/library/ui-styles/src/main/res/color/checkbox_tint_selector.xml @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/library/ui-styles/src/main/res/drawable/bg_shadow_divider.xml b/library/ui-styles/src/main/res/drawable/bg_shadow_divider.xml new file mode 100644 index 0000000000..9d0ef632ec --- /dev/null +++ b/library/ui-styles/src/main/res/drawable/bg_shadow_divider.xml @@ -0,0 +1,9 @@ + + + + \ No newline at end of file diff --git a/library/ui-styles/src/main/res/values/colors.xml b/library/ui-styles/src/main/res/values/colors.xml index 75b03a7d2e..d887e7774e 100644 --- a/library/ui-styles/src/main/res/values/colors.xml +++ b/library/ui-styles/src/main/res/values/colors.xml @@ -122,6 +122,10 @@ @color/palette_gray_100 @color/palette_gray_450 + + @color/palette_element_green + @color/palette_element_green + @color/palette_prune diff --git a/library/ui-styles/src/main/res/values/dimens.xml b/library/ui-styles/src/main/res/values/dimens.xml index 600c73c878..81d5a77297 100644 --- a/library/ui-styles/src/main/res/values/dimens.xml +++ b/library/ui-styles/src/main/res/values/dimens.xml @@ -40,6 +40,7 @@ 24dp 48dp 48dp + 38dp 56dp @@ -67,4 +68,6 @@ 16dp 12dp 8dp + 12dp + 22dp diff --git a/library/ui-styles/src/main/res/values/palette_mobile.xml b/library/ui-styles/src/main/res/values/palette_mobile.xml index ec2f1d0814..5610771f8a 100644 --- a/library/ui-styles/src/main/res/values/palette_mobile.xml +++ b/library/ui-styles/src/main/res/values/palette_mobile.xml @@ -53,5 +53,4 @@ @color/palette_verde @color/palette_azure @color/palette_grape - \ No newline at end of file diff --git a/library/ui-styles/src/main/res/values/stylable_audio_waveform_view.xml b/library/ui-styles/src/main/res/values/stylable_audio_waveform_view.xml new file mode 100644 index 0000000000..f2c703764a --- /dev/null +++ b/library/ui-styles/src/main/res/values/stylable_audio_waveform_view.xml @@ -0,0 +1,22 @@ + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/library/ui-styles/src/main/res/values/styles_text_input_layout.xml b/library/ui-styles/src/main/res/values/styles_text_input_layout.xml index 95a406ea5c..d8e7d598d5 100644 --- a/library/ui-styles/src/main/res/values/styles_text_input_layout.xml +++ b/library/ui-styles/src/main/res/values/styles_text_input_layout.xml @@ -9,6 +9,11 @@ ?vctr_content_secondary + + \ No newline at end of file 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 06670ccd68..7177687fdd 100644 --- a/library/ui-styles/src/main/res/values/theme_dark.xml +++ b/library/ui-styles/src/main/res/values/theme_dark.xml @@ -43,6 +43,7 @@ @color/vctr_presence_indicator_offline_dark + @color/vctr_presence_indicator_online_dark ?vctr_system diff --git a/library/ui-styles/src/main/res/values/theme_light.xml b/library/ui-styles/src/main/res/values/theme_light.xml index c184464320..c90c021591 100644 --- a/library/ui-styles/src/main/res/values/theme_light.xml +++ b/library/ui-styles/src/main/res/values/theme_light.xml @@ -43,6 +43,7 @@ @color/vctr_presence_indicator_offline_light + @color/vctr_presence_indicator_online_light ?vctr_system diff --git a/matrix-sdk-android-flow/src/main/java/org/matrix/android/sdk/flow/FlowExt.kt b/matrix-sdk-android-flow/src/main/java/org/matrix/android/sdk/flow/FlowExt.kt index 72493325c3..d21e898c1d 100644 --- a/matrix-sdk-android-flow/src/main/java/org/matrix/android/sdk/flow/FlowExt.kt +++ b/matrix-sdk-android-flow/src/main/java/org/matrix/android/sdk/flow/FlowExt.kt @@ -1,5 +1,5 @@ /* - * Copyright (c) 2021 New Vector Ltd + * 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. diff --git a/matrix-sdk-android-flow/src/main/java/org/matrix/android/sdk/flow/FlowRoom.kt b/matrix-sdk-android-flow/src/main/java/org/matrix/android/sdk/flow/FlowRoom.kt index d737715306..0f0153bc23 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 @@ -108,12 +108,14 @@ class FlowRoom(private val room: Room) { room.getAllThreadSummaries() } } + fun liveThreadList(): Flow> { return room.getAllThreadsLive().asFlow() .startWith(room.coroutineDispatchers.io) { room.getAllThreads() } } + fun liveLocalUnreadThreadList(): Flow> { return room.getMarkedThreadNotificationsLive().asFlow() .startWith(room.coroutineDispatchers.io) { 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 669e27edfd..9f260858f6 100644 --- a/matrix-sdk-android-flow/src/main/java/org/matrix/android/sdk/flow/FlowSession.kt +++ b/matrix-sdk-android-flow/src/main/java/org/matrix/android/sdk/flow/FlowSession.kt @@ -23,6 +23,9 @@ import org.matrix.android.sdk.api.query.QueryStringValue 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 +import org.matrix.android.sdk.api.session.crypto.crosssigning.PrivateKeysInfo +import org.matrix.android.sdk.api.session.crypto.model.CryptoDeviceInfo +import org.matrix.android.sdk.api.session.crypto.model.DeviceInfo import org.matrix.android.sdk.api.session.group.GroupSummaryQueryParams import org.matrix.android.sdk.api.session.group.model.GroupSummary import org.matrix.android.sdk.api.session.identity.ThreePid @@ -39,23 +42,20 @@ import org.matrix.android.sdk.api.session.user.model.User import org.matrix.android.sdk.api.session.widgets.model.Widget import org.matrix.android.sdk.api.util.Optional import org.matrix.android.sdk.api.util.toOptional -import org.matrix.android.sdk.internal.crypto.model.CryptoDeviceInfo -import org.matrix.android.sdk.internal.crypto.model.rest.DeviceInfo -import org.matrix.android.sdk.internal.crypto.store.PrivateKeysInfo class FlowSession(private val session: Session) { fun liveRoomSummaries(queryParams: RoomSummaryQueryParams, sortOrder: RoomSortOrder = RoomSortOrder.NONE): Flow> { - return session.getRoomSummariesLive(queryParams, sortOrder).asFlow() + return session.roomService().getRoomSummariesLive(queryParams, sortOrder).asFlow() .startWith(session.coroutineDispatchers.io) { - session.getRoomSummaries(queryParams, sortOrder) + session.roomService().getRoomSummaries(queryParams, sortOrder) } } fun liveGroupSummaries(queryParams: GroupSummaryQueryParams): Flow> { - return session.getGroupSummariesLive(queryParams).asFlow() + return session.groupService().getGroupSummariesLive(queryParams).asFlow() .startWith(session.coroutineDispatchers.io) { - session.getGroupSummaries(queryParams) + session.groupService().getGroupSummaries(queryParams) } } @@ -67,9 +67,9 @@ class FlowSession(private val session: Session) { } fun liveBreadcrumbs(queryParams: RoomSummaryQueryParams): Flow> { - return session.getBreadcrumbsLive(queryParams).asFlow() + return session.roomService().getBreadcrumbsLive(queryParams).asFlow() .startWith(session.coroutineDispatchers.io) { - session.getBreadcrumbs(queryParams) + session.roomService().getBreadcrumbs(queryParams) } } @@ -85,43 +85,47 @@ class FlowSession(private val session: Session) { } fun livePushers(): Flow> { - return session.getPushersLive().asFlow() + return session.pushersService().getPushersLive().asFlow() } fun liveUser(userId: String): Flow> { - return session.getUserLive(userId).asFlow() + return session.userService().getUserLive(userId).asFlow() .startWith(session.coroutineDispatchers.io) { - session.getUser(userId).toOptional() + session.userService().getUser(userId).toOptional() } } fun liveRoomMember(userId: String, roomId: String): Flow> { - return session.getRoomMemberLive(userId, roomId).asFlow() + return session.roomService().getRoomMemberLive(userId, roomId).asFlow() .startWith(session.coroutineDispatchers.io) { - session.getRoomMember(userId, roomId).toOptional() + session.roomService().getRoomMember(userId, roomId).toOptional() } } fun liveUsers(): Flow> { - return session.getUsersLive().asFlow() + return session.userService().getUsersLive().asFlow() } fun liveIgnoredUsers(): Flow> { - return session.getIgnoredUsersLive().asFlow() + return session.userService().getIgnoredUsersLive().asFlow() } fun livePagedUsers(filter: String? = null, excludedUserIds: Set? = null): Flow> { - return session.getPagedUsersLive(filter, excludedUserIds).asFlow() + return session.userService().getPagedUsersLive(filter, excludedUserIds).asFlow() } fun liveThreePIds(refreshData: Boolean): Flow> { - return session.getThreePidsLive(refreshData).asFlow() - .startWith(session.coroutineDispatchers.io) { session.getThreePids() } + return session.profileService().getThreePidsLive(refreshData).asFlow() + .startWith(session.coroutineDispatchers.io) { + session.profileService().getThreePids() + } } fun livePendingThreePIds(): Flow> { - return session.getPendingThreePidsLive().asFlow() - .startWith(session.coroutineDispatchers.io) { session.getPendingThreePids() } + return session.profileService().getPendingThreePidsLive().asFlow() + .startWith(session.coroutineDispatchers.io) { + session.profileService().getPendingThreePids() + } } fun liveUserCryptoDevices(userId: String): Flow> { @@ -179,7 +183,7 @@ class FlowSession(private val session: Session) { } fun liveRoomChangeMembershipState(): Flow> { - return session.getChangeMembershipsLive().asFlow() + return session.roomService().getChangeMembershipsLive().asFlow() } } diff --git a/matrix-sdk-android-flow/src/main/java/org/matrix/android/sdk/flow/OptionalFlow.kt b/matrix-sdk-android-flow/src/main/java/org/matrix/android/sdk/flow/OptionalFlow.kt index a9f062f379..42d84ed535 100644 --- a/matrix-sdk-android-flow/src/main/java/org/matrix/android/sdk/flow/OptionalFlow.kt +++ b/matrix-sdk-android-flow/src/main/java/org/matrix/android/sdk/flow/OptionalFlow.kt @@ -1,5 +1,5 @@ /* - * Copyright (c) 2021 New Vector Ltd + * 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. diff --git a/matrix-sdk-android/build.gradle b/matrix-sdk-android/build.gradle index 2b2c38e22a..a4a3dcee80 100644 --- a/matrix-sdk-android/build.gradle +++ b/matrix-sdk-android/build.gradle @@ -3,6 +3,7 @@ apply plugin: 'kotlin-android' apply plugin: 'kotlin-kapt' apply plugin: 'kotlin-parcelize' apply plugin: 'realm-android' +apply plugin: "org.jetbrains.dokka" buildscript { repositories { @@ -13,6 +14,22 @@ buildscript { } } +dokkaHtml { + dokkaSourceSets { + configureEach { + // Emit warnings about not documented members. + reportUndocumented.set(true) + // Suppress package legacy riot. Sadly this does not work + /* + perPackageOption { + prefix.set("org.matrix.android.sdk.internal.legacy.riot") + suppress.set(true) + } + */ + } + } +} + android { testOptions.unitTests.includeAndroidResources = true @@ -31,7 +48,7 @@ android { // that the app's state is completely cleared between tests. testInstrumentationRunnerArguments clearPackageData: 'true' - buildConfigField "String", "SDK_VERSION", "\"1.4.6\"" + buildConfigField "String", "SDK_VERSION", "\"1.4.14\"" buildConfigField "String", "GIT_SDK_REVISION", "\"${gitRevision()}\"" buildConfigField "String", "GIT_SDK_REVISION_UNIX_DATE", "\"${gitRevisionUnixDate()}\"" @@ -73,6 +90,10 @@ android { kotlinOptions { jvmTarget = "11" + freeCompilerArgs += [ + // Disabled for now, there are too many errors. Could be handled in another dedicated PR + // '-Xexplicit-api=strict', // or warning + ] } sourceSets { @@ -166,7 +187,7 @@ dependencies { implementation libs.apache.commonsImaging // Phone number https://github.com/google/libphonenumber - implementation 'com.googlecode.libphonenumber:libphonenumber:8.12.45' + implementation 'com.googlecode.libphonenumber:libphonenumber:8.12.47' testImplementation libs.tests.junit testImplementation 'org.robolectric:robolectric:4.7.3' diff --git a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/account/ChangePasswordTest.kt b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/account/ChangePasswordTest.kt index 933074cdce..6d740c5a34 100644 --- a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/account/ChangePasswordTest.kt +++ b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/account/ChangePasswordTest.kt @@ -46,7 +46,7 @@ class ChangePasswordTest : InstrumentedTest { // Change password commonTestHelper.runBlockingTest { - session.changePassword(TestConstants.PASSWORD, NEW_PASSWORD) + session.accountService().changePassword(TestConstants.PASSWORD, NEW_PASSWORD) } // Try to login with the previous password, it will fail diff --git a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/account/DeactivateAccountTest.kt b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/account/DeactivateAccountTest.kt index f8d108fb73..52dbfc7155 100644 --- a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/account/DeactivateAccountTest.kt +++ b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/account/DeactivateAccountTest.kt @@ -47,7 +47,7 @@ class DeactivateAccountTest : InstrumentedTest { // Deactivate the account commonTestHelper.runBlockingTest { - session.deactivateAccount( + session.accountService().deactivateAccount( eraseAllData = false, userInteractiveAuthInterceptor = object : UserInteractiveAuthInterceptor { override fun performStage(flowResponse: RegistrationFlowResponse, errCode: String?, promise: Continuation) { diff --git a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/common/CommonTestHelper.kt b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/common/CommonTestHelper.kt index ac4ccf56d1..63922f0226 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 @@ -431,7 +431,7 @@ class CommonTestHelper(context: Context) { fun signOutAndClose(session: Session) { runBlockingTest(timeout = 60_000) { - session.signOut(true) + session.signOutService().signOut(true) } // no need signout will close // session.close() diff --git a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/common/CryptoTestHelper.kt b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/common/CryptoTestHelper.kt index 71796192a8..ecb279cdc2 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 @@ -27,8 +27,12 @@ import org.matrix.android.sdk.api.auth.UIABaseAuth import org.matrix.android.sdk.api.auth.UserInteractiveAuthInterceptor import org.matrix.android.sdk.api.auth.UserPasswordAuth import org.matrix.android.sdk.api.auth.registration.RegistrationFlowResponse +import org.matrix.android.sdk.api.crypto.MXCRYPTO_ALGORITHM_MEGOLM +import org.matrix.android.sdk.api.crypto.MXCRYPTO_ALGORITHM_MEGOLM_BACKUP import org.matrix.android.sdk.api.extensions.orFalse import org.matrix.android.sdk.api.session.Session +import org.matrix.android.sdk.api.session.crypto.keysbackup.MegolmBackupAuthData +import org.matrix.android.sdk.api.session.crypto.keysbackup.MegolmBackupCreationInfo import org.matrix.android.sdk.api.session.crypto.verification.IncomingSasVerificationTransaction import org.matrix.android.sdk.api.session.crypto.verification.OutgoingSasVerificationTransaction import org.matrix.android.sdk.api.session.crypto.verification.VerificationMethod @@ -36,16 +40,13 @@ import org.matrix.android.sdk.api.session.crypto.verification.VerificationTxStat import org.matrix.android.sdk.api.session.events.model.Event import org.matrix.android.sdk.api.session.events.model.EventType import org.matrix.android.sdk.api.session.events.model.toContent +import org.matrix.android.sdk.api.session.getRoom import org.matrix.android.sdk.api.session.room.Room import org.matrix.android.sdk.api.session.room.model.Membership import org.matrix.android.sdk.api.session.room.model.RoomSummary import org.matrix.android.sdk.api.session.room.model.create.CreateRoomParams import org.matrix.android.sdk.api.session.room.roomSummaryQueryParams import org.matrix.android.sdk.api.util.Optional -import org.matrix.android.sdk.internal.crypto.MXCRYPTO_ALGORITHM_MEGOLM -import org.matrix.android.sdk.internal.crypto.MXCRYPTO_ALGORITHM_MEGOLM_BACKUP -import org.matrix.android.sdk.internal.crypto.keysbackup.model.MegolmBackupAuthData -import org.matrix.android.sdk.internal.crypto.keysbackup.model.MegolmBackupCreationInfo import java.util.UUID import kotlin.coroutines.Continuation import kotlin.coroutines.resume @@ -64,7 +65,7 @@ class CryptoTestHelper(private val testHelper: CommonTestHelper) { val aliceSession = testHelper.createAccount(TestConstants.USER_ALICE, defaultSessionParams) val roomId = testHelper.runBlockingTest { - aliceSession.createRoom(CreateRoomParams().apply { name = "MyRoom" }) + aliceSession.roomService().createRoom(CreateRoomParams().apply { name = "MyRoom" }) } if (encryptedRoom) { testHelper.waitWithLatch { latch -> @@ -98,7 +99,7 @@ class CryptoTestHelper(private val testHelper: CommonTestHelper) { val bobSession = testHelper.createAccount(TestConstants.USER_BOB, defaultSessionParams) testHelper.waitWithLatch { latch -> - val bobRoomSummariesLive = bobSession.getRoomSummariesLive(roomSummaryQueryParams { }) + val bobRoomSummariesLive = bobSession.roomService().getRoomSummariesLive(roomSummaryQueryParams { }) val newRoomObserver = object : Observer> { override fun onChanged(t: List?) { if (t?.isNotEmpty() == true) { @@ -112,7 +113,7 @@ class CryptoTestHelper(private val testHelper: CommonTestHelper) { } testHelper.waitWithLatch { latch -> - val bobRoomSummariesLive = bobSession.getRoomSummariesLive(roomSummaryQueryParams { }) + val bobRoomSummariesLive = bobSession.roomService().getRoomSummariesLive(roomSummaryQueryParams { }) val roomJoinedObserver = object : Observer> { override fun onChanged(t: List?) { if (bobSession.getRoom(aliceRoomId) @@ -124,7 +125,7 @@ class CryptoTestHelper(private val testHelper: CommonTestHelper) { } } bobRoomSummariesLive.observeForever(roomJoinedObserver) - bobSession.joinRoom(aliceRoomId) + bobSession.roomService().joinRoom(aliceRoomId) } // Ensure bob can send messages to the room // val roomFromBobPOV = bobSession.getRoom(aliceRoomId)!! @@ -164,7 +165,7 @@ class CryptoTestHelper(private val testHelper: CommonTestHelper) { } testHelper.runBlockingTest { - samSession.joinRoom(room.roomId, null, emptyList()) + samSession.roomService().joinRoom(room.roomId, null, emptyList()) } return samSession @@ -242,8 +243,8 @@ class CryptoTestHelper(private val testHelper: CommonTestHelper) { fun createDM(alice: Session, bob: Session): String { var roomId: String = "" testHelper.waitWithLatch { latch -> - roomId = alice.createDirectRoom(bob.myUserId) - val bobRoomSummariesLive = bob.getRoomSummariesLive(roomSummaryQueryParams { }) + roomId = alice.roomService().createDirectRoom(bob.myUserId) + val bobRoomSummariesLive = bob.roomService().getRoomSummariesLive(roomSummaryQueryParams { }) val newRoomObserver = object : Observer> { override fun onChanged(t: List?) { if (t?.any { it.roomId == roomId }.orFalse()) { @@ -256,7 +257,7 @@ class CryptoTestHelper(private val testHelper: CommonTestHelper) { } testHelper.waitWithLatch { latch -> - val bobRoomSummariesLive = bob.getRoomSummariesLive(roomSummaryQueryParams { }) + val bobRoomSummariesLive = bob.roomService().getRoomSummariesLive(roomSummaryQueryParams { }) val newRoomObserver = object : Observer> { override fun onChanged(t: List?) { if (bob.getRoom(roomId) @@ -268,7 +269,7 @@ class CryptoTestHelper(private val testHelper: CommonTestHelper) { } } bobRoomSummariesLive.observeForever(newRoomObserver) - bob.joinRoom(roomId) + bob.roomService().joinRoom(roomId) } return roomId @@ -367,7 +368,7 @@ class CryptoTestHelper(private val testHelper: CommonTestHelper) { aliceSession.cryptoService().setWarnOnUnknownDevices(false) val roomId = testHelper.runBlockingTest { - aliceSession.createRoom(CreateRoomParams().apply { name = "MyRoom" }) + aliceSession.roomService().createRoom(CreateRoomParams().apply { name = "MyRoom" }) } val room = aliceSession.getRoom(roomId)!! @@ -380,7 +381,7 @@ class CryptoTestHelper(private val testHelper: CommonTestHelper) { val session = testHelper.createAccount("User_$index", defaultSessionParams) testHelper.runBlockingTest(timeout = 600_000) { room.invite(session.myUserId, null) } println("TEST -> " + session.myUserId + " invited") - testHelper.runBlockingTest { session.joinRoom(room.roomId, null, emptyList()) } + testHelper.runBlockingTest { session.roomService().joinRoom(room.roomId, null, emptyList()) } println("TEST -> " + session.myUserId + " joined") sessions.add(session) } diff --git a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/common/TestMatrixComponent.kt b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/common/TestMatrixComponent.kt index d0f0e23152..dc58339498 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 @@ -26,6 +26,7 @@ import org.matrix.android.sdk.internal.di.MatrixModule import org.matrix.android.sdk.internal.di.MatrixScope import org.matrix.android.sdk.internal.di.NetworkModule import org.matrix.android.sdk.internal.raw.RawModule +import org.matrix.android.sdk.internal.settings.SettingsModule import org.matrix.android.sdk.internal.util.system.SystemModule @Component(modules = [ @@ -34,6 +35,7 @@ import org.matrix.android.sdk.internal.util.system.SystemModule NetworkModule::class, AuthModule::class, RawModule::class, + SettingsModule::class, SystemModule::class ]) @MatrixScope 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 aaf779212b..732f4f7dce 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 @@ -25,10 +25,10 @@ import org.junit.FixMethodOrder import org.junit.Test import org.junit.runner.RunWith import org.junit.runners.MethodSorters +import org.matrix.android.sdk.api.session.crypto.attachments.toElementToDecrypt +import org.matrix.android.sdk.api.session.crypto.model.EncryptedFileInfo +import org.matrix.android.sdk.api.session.crypto.model.EncryptedFileKey import org.matrix.android.sdk.internal.crypto.attachments.MXEncryptedAttachments -import org.matrix.android.sdk.internal.crypto.attachments.toElementToDecrypt -import org.matrix.android.sdk.internal.crypto.model.rest.EncryptedFileInfo -import org.matrix.android.sdk.internal.crypto.model.rest.EncryptedFileKey import java.io.ByteArrayOutputStream import java.io.InputStream 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 41ec69cdc5..fbd0905261 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 @@ -30,8 +30,16 @@ import org.junit.runners.MethodSorters import org.matrix.android.sdk.InstrumentedTest import org.matrix.android.sdk.api.session.Session import org.matrix.android.sdk.api.session.crypto.MXCryptoError +import org.matrix.android.sdk.api.session.crypto.keysbackup.KeysVersion +import org.matrix.android.sdk.api.session.crypto.keysbackup.KeysVersionResult +import org.matrix.android.sdk.api.session.crypto.keysbackup.MegolmBackupCreationInfo +import org.matrix.android.sdk.api.session.crypto.model.ImportRoomKeysResult +import org.matrix.android.sdk.api.session.crypto.model.OlmDecryptionResult import org.matrix.android.sdk.api.session.events.model.EventType +import org.matrix.android.sdk.api.session.events.model.content.EncryptedEventContent import org.matrix.android.sdk.api.session.events.model.toModel +import org.matrix.android.sdk.api.session.getRoom +import org.matrix.android.sdk.api.session.getRoomSummary import org.matrix.android.sdk.api.session.room.Room import org.matrix.android.sdk.api.session.room.failure.JoinRoomFailure import org.matrix.android.sdk.api.session.room.model.Membership @@ -43,12 +51,6 @@ import org.matrix.android.sdk.common.CryptoTestHelper import org.matrix.android.sdk.common.SessionTestParams import org.matrix.android.sdk.common.TestConstants import org.matrix.android.sdk.common.TestMatrixCallback -import org.matrix.android.sdk.internal.crypto.algorithms.olm.OlmDecryptionResult -import org.matrix.android.sdk.internal.crypto.keysbackup.model.MegolmBackupCreationInfo -import org.matrix.android.sdk.internal.crypto.keysbackup.model.rest.KeysVersion -import org.matrix.android.sdk.internal.crypto.keysbackup.model.rest.KeysVersionResult -import org.matrix.android.sdk.internal.crypto.model.ImportRoomKeysResult -import org.matrix.android.sdk.internal.crypto.model.event.EncryptedEventContent @RunWith(JUnit4::class) @FixMethodOrder(MethodSorters.JVM) @@ -551,7 +553,7 @@ class E2eeSanityTests : InstrumentedTest { testHelper.waitWithLatch { latch -> testHelper.retryPeriodicallyWithLatch(latch) { otherAccounts.map { - aliceSession.getRoomMember(it.myUserId, e2eRoomID)?.membership + aliceSession.roomService().getRoomMember(it.myUserId, e2eRoomID)?.membership }.all { it == Membership.JOIN } @@ -574,7 +576,7 @@ class E2eeSanityTests : InstrumentedTest { testHelper.runBlockingTest(60_000) { Log.v("#E2E TEST", "${otherSession.myUserId} tries to join room $e2eRoomID") try { - otherSession.joinRoom(e2eRoomID) + otherSession.roomService().joinRoom(e2eRoomID) } catch (ex: JoinRoomFailure.JoinedWithTimeout) { // it's ok we will wait after } diff --git a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/PreShareKeysTest.kt b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/PreShareKeysTest.kt index 46c1dacf78..f8ce7ae357 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 @@ -26,11 +26,12 @@ 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.EventType +import org.matrix.android.sdk.api.session.events.model.content.EncryptedEventContent +import org.matrix.android.sdk.api.session.events.model.content.RoomKeyContent import org.matrix.android.sdk.api.session.events.model.toModel +import org.matrix.android.sdk.api.session.getRoom import org.matrix.android.sdk.common.CommonTestHelper import org.matrix.android.sdk.common.CryptoTestHelper -import org.matrix.android.sdk.internal.crypto.model.event.EncryptedEventContent -import org.matrix.android.sdk.internal.crypto.model.event.RoomKeyContent @RunWith(AndroidJUnit4::class) @FixMethodOrder(MethodSorters.JVM) @@ -90,7 +91,7 @@ class PreShareKeysTest : InstrumentedTest { // Just send a real message as test val sentEvent = testHelper.sendTextMessage(aliceSession.getRoom(e2eRoomID)!!, "Allo", 1).first() - assertEquals("Unexpected megolm session", megolmSessionId, sentEvent.root.content.toModel()?.sessionId,) + assertEquals("Unexpected megolm session", megolmSessionId, sentEvent.root.content.toModel()?.sessionId) testHelper.waitWithLatch { latch -> testHelper.retryPeriodicallyWithLatch(latch) { bobSession.getRoom(e2eRoomID)?.getTimelineEvent(sentEvent.eventId)?.root?.getClearType() == EventType.MESSAGE 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 fb5d58b127..8e9f651f4c 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 @@ -32,7 +32,9 @@ import org.matrix.android.sdk.api.auth.registration.RegistrationFlowResponse import org.matrix.android.sdk.api.extensions.tryOrNull import org.matrix.android.sdk.api.session.crypto.MXCryptoError import org.matrix.android.sdk.api.session.events.model.EventType +import org.matrix.android.sdk.api.session.events.model.content.EncryptedEventContent import org.matrix.android.sdk.api.session.events.model.toModel +import org.matrix.android.sdk.api.session.getRoom import org.matrix.android.sdk.api.session.room.timeline.Timeline import org.matrix.android.sdk.api.session.room.timeline.TimelineEvent import org.matrix.android.sdk.api.session.room.timeline.TimelineSettings @@ -40,7 +42,6 @@ import org.matrix.android.sdk.common.CommonTestHelper import org.matrix.android.sdk.common.CryptoTestHelper import org.matrix.android.sdk.common.TestConstants import org.matrix.android.sdk.internal.crypto.model.OlmSessionWrapper -import org.matrix.android.sdk.internal.crypto.model.event.EncryptedEventContent import org.matrix.android.sdk.internal.crypto.store.db.deserializeFromRealm import org.matrix.android.sdk.internal.crypto.store.db.serializeForRealm import org.matrix.olm.OlmSession diff --git a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/crosssigning/ExtensionsKtTest.kt b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/crosssigning/ExtensionsKtTest.kt index 9fa7458ea7..936dc6a872 100644 --- a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/crosssigning/ExtensionsKtTest.kt +++ b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/crosssigning/ExtensionsKtTest.kt @@ -19,6 +19,7 @@ package org.matrix.android.sdk.internal.crypto.crosssigning import org.amshove.kluent.shouldBeNull import org.amshove.kluent.shouldBeTrue import org.junit.Test +import org.matrix.android.sdk.api.util.fromBase64 @Suppress("SpellCheckingInspection") class ExtensionsKtTest { 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 a6e8f94c91..0f3ff7898f 100644 --- a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/crosssigning/XSigningTest.kt +++ b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/crosssigning/XSigningTest.kt @@ -34,12 +34,14 @@ import org.matrix.android.sdk.api.auth.UIABaseAuth import org.matrix.android.sdk.api.auth.UserInteractiveAuthInterceptor import org.matrix.android.sdk.api.auth.UserPasswordAuth import org.matrix.android.sdk.api.auth.registration.RegistrationFlowResponse +import org.matrix.android.sdk.api.session.crypto.crosssigning.isCrossSignedVerified +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.SessionTestParams import org.matrix.android.sdk.common.TestConstants -import org.matrix.android.sdk.internal.crypto.model.CryptoDeviceInfo -import org.matrix.android.sdk.internal.crypto.model.MXUsersDevicesMap import kotlin.coroutines.Continuation import kotlin.coroutines.resume @@ -108,11 +110,13 @@ class XSigningTest : InstrumentedTest { } }, it) } - testHelper.doSync { bobSession.cryptoService().crossSigningService().initializeCrossSigning(object : UserInteractiveAuthInterceptor { - override fun performStage(flowResponse: RegistrationFlowResponse, errCode: String?, promise: Continuation) { - promise.resume(bobAuthParams) - } - }, it) } + testHelper.doSync { + bobSession.cryptoService().crossSigningService().initializeCrossSigning(object : UserInteractiveAuthInterceptor { + override fun performStage(flowResponse: RegistrationFlowResponse, errCode: String?, promise: Continuation) { + promise.resume(bobAuthParams) + } + }, it) + } // Check that alice can see bob keys testHelper.doSync> { aliceSession.cryptoService().downloadKeys(listOf(bobSession.myUserId), true, it) } @@ -147,16 +151,20 @@ class XSigningTest : InstrumentedTest { password = TestConstants.PASSWORD ) - testHelper.doSync { aliceSession.cryptoService().crossSigningService().initializeCrossSigning(object : UserInteractiveAuthInterceptor { - override fun performStage(flowResponse: RegistrationFlowResponse, errCode: String?, promise: Continuation) { - promise.resume(aliceAuthParams) - } - }, it) } - testHelper.doSync { bobSession.cryptoService().crossSigningService().initializeCrossSigning(object : UserInteractiveAuthInterceptor { - override fun performStage(flowResponse: RegistrationFlowResponse, errCode: String?, promise: Continuation) { - promise.resume(bobAuthParams) - } - }, it) } + testHelper.doSync { + aliceSession.cryptoService().crossSigningService().initializeCrossSigning(object : UserInteractiveAuthInterceptor { + override fun performStage(flowResponse: RegistrationFlowResponse, errCode: String?, promise: Continuation) { + promise.resume(aliceAuthParams) + } + }, it) + } + testHelper.doSync { + bobSession.cryptoService().crossSigningService().initializeCrossSigning(object : UserInteractiveAuthInterceptor { + override fun performStage(flowResponse: RegistrationFlowResponse, errCode: String?, promise: Continuation) { + promise.resume(bobAuthParams) + } + }, it) + } // Check that alice can see bob keys val bobUserId = bobSession.myUserId diff --git a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/encryption/EncryptionTest.kt b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/encryption/EncryptionTest.kt index 060201d624..f130918bd8 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 @@ -24,8 +24,11 @@ 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.crypto.MXCRYPTO_ALGORITHM_MEGOLM import org.matrix.android.sdk.api.session.events.model.EventType +import org.matrix.android.sdk.api.session.events.model.content.EncryptionEventContent import org.matrix.android.sdk.api.session.events.model.toContent +import org.matrix.android.sdk.api.session.getRoom import org.matrix.android.sdk.api.session.room.Room import org.matrix.android.sdk.api.session.room.send.SendState import org.matrix.android.sdk.api.session.room.timeline.Timeline @@ -33,8 +36,6 @@ 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.internal.crypto.MXCRYPTO_ALGORITHM_MEGOLM -import org.matrix.android.sdk.internal.crypto.model.event.EncryptionEventContent import java.util.concurrent.CountDownLatch @RunWith(AndroidJUnit4::class) 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 cd20ab477c..ed30691175 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 @@ -35,27 +35,28 @@ import org.matrix.android.sdk.api.auth.UserInteractiveAuthInterceptor import org.matrix.android.sdk.api.auth.UserPasswordAuth import org.matrix.android.sdk.api.auth.registration.RegistrationFlowResponse import org.matrix.android.sdk.api.extensions.tryOrNull +import org.matrix.android.sdk.api.session.crypto.crosssigning.DeviceTrustLevel +import org.matrix.android.sdk.api.session.crypto.keysbackup.KeysVersion +import org.matrix.android.sdk.api.session.crypto.keysbackup.MegolmBackupCreationInfo +import org.matrix.android.sdk.api.session.crypto.model.CryptoDeviceInfo +import org.matrix.android.sdk.api.session.crypto.model.GossipingRequestState +import org.matrix.android.sdk.api.session.crypto.model.MXUsersDevicesMap +import org.matrix.android.sdk.api.session.crypto.model.OutgoingGossipingRequestState import org.matrix.android.sdk.api.session.crypto.verification.IncomingSasVerificationTransaction import org.matrix.android.sdk.api.session.crypto.verification.SasVerificationTransaction import org.matrix.android.sdk.api.session.crypto.verification.VerificationMethod import org.matrix.android.sdk.api.session.crypto.verification.VerificationService import org.matrix.android.sdk.api.session.crypto.verification.VerificationTransaction import org.matrix.android.sdk.api.session.crypto.verification.VerificationTxState +import org.matrix.android.sdk.api.session.events.model.content.EncryptedEventContent import org.matrix.android.sdk.api.session.events.model.toModel +import org.matrix.android.sdk.api.session.getRoom import org.matrix.android.sdk.api.session.room.model.RoomDirectoryVisibility import org.matrix.android.sdk.api.session.room.model.create.CreateRoomParams import org.matrix.android.sdk.api.session.room.model.message.MessageContent import org.matrix.android.sdk.common.CommonTestHelper import org.matrix.android.sdk.common.SessionTestParams import org.matrix.android.sdk.common.TestConstants -import org.matrix.android.sdk.internal.crypto.GossipingRequestState -import org.matrix.android.sdk.internal.crypto.OutgoingGossipingRequestState -import org.matrix.android.sdk.internal.crypto.crosssigning.DeviceTrustLevel -import org.matrix.android.sdk.internal.crypto.keysbackup.model.MegolmBackupCreationInfo -import org.matrix.android.sdk.internal.crypto.keysbackup.model.rest.KeysVersion -import org.matrix.android.sdk.internal.crypto.model.CryptoDeviceInfo -import org.matrix.android.sdk.internal.crypto.model.MXUsersDevicesMap -import org.matrix.android.sdk.internal.crypto.model.event.EncryptedEventContent import kotlin.coroutines.Continuation import kotlin.coroutines.resume @@ -73,7 +74,7 @@ class KeyShareTests : InstrumentedTest { // Create an encrypted room and add a message val roomId = commonTestHelper.runBlockingTest { - aliceSession.createRoom( + aliceSession.roomService().createRoom( CreateRoomParams().apply { visibility = RoomDirectoryVisibility.PRIVATE enableEncryption() @@ -112,7 +113,7 @@ class KeyShareTests : InstrumentedTest { var outGoingRequestId: String? = null - commonTestHelper.waitWithLatch { latch -> + commonTestHelper.waitWithLatch { latch -> commonTestHelper.retryPeriodicallyWithLatch(latch) { aliceSession2.cryptoService().getOutgoingRoomKeyRequests() .filter { req -> @@ -340,7 +341,7 @@ class KeyShareTests : InstrumentedTest { // Create an encrypted room and send a couple of messages val roomId = commonTestHelper.runBlockingTest { - aliceSession.createRoom( + aliceSession.roomService().createRoom( CreateRoomParams().apply { visibility = RoomDirectoryVisibility.PRIVATE enableEncryption() @@ -378,7 +379,7 @@ class KeyShareTests : InstrumentedTest { } commonTestHelper.runBlockingTest { - bobSession.joinRoom(roomAlicePov.roomId, null, emptyList()) + bobSession.roomService().joinRoom(roomAlicePov.roomId, null, emptyList()) } // we want to discard alice outbound session diff --git a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/gossiping/WithHeldTests.kt b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/gossiping/WithHeldTests.kt index 65c65660b5..8f906c56a7 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 @@ -30,14 +30,15 @@ import org.matrix.android.sdk.api.NoOpMatrixCallback import org.matrix.android.sdk.api.extensions.tryOrNull import org.matrix.android.sdk.api.session.crypto.MXCryptoError import org.matrix.android.sdk.api.session.events.model.EventType +import org.matrix.android.sdk.api.session.events.model.content.EncryptedEventContent +import org.matrix.android.sdk.api.session.events.model.content.WithHeldCode import org.matrix.android.sdk.api.session.events.model.toModel +import org.matrix.android.sdk.api.session.getRoom import org.matrix.android.sdk.common.CommonTestHelper import org.matrix.android.sdk.common.CryptoTestHelper import org.matrix.android.sdk.common.MockOkHttpInterceptor import org.matrix.android.sdk.common.SessionTestParams import org.matrix.android.sdk.common.TestConstants -import org.matrix.android.sdk.internal.crypto.model.event.EncryptedEventContent -import org.matrix.android.sdk.internal.crypto.model.event.WithHeldCode @RunWith(AndroidJUnit4::class) @FixMethodOrder(MethodSorters.JVM) @@ -138,7 +139,7 @@ class WithHeldTests : InstrumentedTest { @Test @Ignore("This test will be ignored until it is fixed") - fun test_WithHeldNoOlm() { + fun test_WithHeldNoOlm() { val testData = cryptoTestHelper.doE2ETestWithAliceAndBobInARoom() val aliceSession = testData.firstSession val bobSession = testData.secondSession!! diff --git a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/keysbackup/KeysBackupScenarioData.kt b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/keysbackup/KeysBackupScenarioData.kt index 864f3c12e4..45fdb9e1e3 100644 --- a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/keysbackup/KeysBackupScenarioData.kt +++ b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/keysbackup/KeysBackupScenarioData.kt @@ -24,10 +24,12 @@ import org.matrix.android.sdk.internal.crypto.model.OlmInboundGroupSessionWrappe /** * Data class to store result of [KeysBackupTestHelper.createKeysBackupScenarioWithPassword] */ -data class KeysBackupScenarioData(val cryptoTestData: CryptoTestData, - val aliceKeys: List, - val prepareKeysBackupDataResult: PrepareKeysBackupDataResult, - val aliceSession2: Session) { +internal data class KeysBackupScenarioData( + val cryptoTestData: CryptoTestData, + val aliceKeys: List, + val prepareKeysBackupDataResult: PrepareKeysBackupDataResult, + val aliceSession2: Session +) { fun cleanUp(testHelper: CommonTestHelper) { cryptoTestData.cleanUp(testHelper) testHelper.signOutAndClose(aliceSession2) 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 4c94566219..3220f161fa 100644 --- a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/keysbackup/KeysBackupTest.kt +++ b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/keysbackup/KeysBackupTest.kt @@ -29,21 +29,23 @@ 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.crypto.MXCRYPTO_ALGORITHM_MEGOLM_BACKUP import org.matrix.android.sdk.api.listeners.ProgressListener import org.matrix.android.sdk.api.listeners.StepProgressListener +import org.matrix.android.sdk.api.session.crypto.crosssigning.DeviceTrustLevel +import org.matrix.android.sdk.api.session.crypto.keysbackup.KeysBackupLastVersionResult import org.matrix.android.sdk.api.session.crypto.keysbackup.KeysBackupState import org.matrix.android.sdk.api.session.crypto.keysbackup.KeysBackupStateListener +import org.matrix.android.sdk.api.session.crypto.keysbackup.KeysBackupVersionTrust +import org.matrix.android.sdk.api.session.crypto.keysbackup.KeysVersion +import org.matrix.android.sdk.api.session.crypto.keysbackup.MegolmBackupCreationInfo +import org.matrix.android.sdk.api.session.crypto.keysbackup.toKeysVersionResult +import org.matrix.android.sdk.api.session.crypto.model.ImportRoomKeysResult +import org.matrix.android.sdk.api.session.getRoom import org.matrix.android.sdk.common.CommonTestHelper import org.matrix.android.sdk.common.CryptoTestHelper import org.matrix.android.sdk.common.TestConstants import org.matrix.android.sdk.common.TestMatrixCallback -import org.matrix.android.sdk.internal.crypto.MXCRYPTO_ALGORITHM_MEGOLM_BACKUP -import org.matrix.android.sdk.internal.crypto.crosssigning.DeviceTrustLevel -import org.matrix.android.sdk.internal.crypto.keysbackup.model.KeysBackupVersionTrust -import org.matrix.android.sdk.internal.crypto.keysbackup.model.MegolmBackupCreationInfo -import org.matrix.android.sdk.internal.crypto.keysbackup.model.rest.KeysVersion -import org.matrix.android.sdk.internal.crypto.keysbackup.model.rest.KeysVersionResult -import org.matrix.android.sdk.internal.crypto.model.ImportRoomKeysResult import java.util.Collections import java.util.concurrent.CountDownLatch @@ -403,9 +405,9 @@ class KeysBackupTest : InstrumentedTest { assertTrue(testData.aliceSession2.cryptoService().keysBackupService().isEnabled) // - Retrieve the last version from the server - val keysVersionResult = testHelper.doSync { + val keysVersionResult = testHelper.doSync { testData.aliceSession2.cryptoService().keysBackupService().getCurrentVersion(it) - } + }.toKeysVersionResult() // - It must be the same assertEquals(testData.prepareKeysBackupDataResult.version, keysVersionResult!!.version) @@ -463,9 +465,9 @@ class KeysBackupTest : InstrumentedTest { assertTrue(testData.aliceSession2.cryptoService().keysBackupService().isEnabled) // - Retrieve the last version from the server - val keysVersionResult = testHelper.doSync { + val keysVersionResult = testHelper.doSync { testData.aliceSession2.cryptoService().keysBackupService().getCurrentVersion(it) - } + }.toKeysVersionResult() // - It must be the same assertEquals(testData.prepareKeysBackupDataResult.version, keysVersionResult!!.version) @@ -565,9 +567,9 @@ class KeysBackupTest : InstrumentedTest { assertTrue(testData.aliceSession2.cryptoService().keysBackupService().isEnabled) // - Retrieve the last version from the server - val keysVersionResult = testHelper.doSync { + val keysVersionResult = testHelper.doSync { testData.aliceSession2.cryptoService().keysBackupService().getCurrentVersion(it) - } + }.toKeysVersionResult() // - It must be the same assertEquals(testData.prepareKeysBackupDataResult.version, keysVersionResult!!.version) @@ -835,9 +837,9 @@ class KeysBackupTest : InstrumentedTest { keysBackupTestHelper.prepareAndCreateKeysBackupData(keysBackup) // Get key backup version from the homeserver - val keysVersionResult = testHelper.doSync { + val keysVersionResult = testHelper.doSync { keysBackup.getCurrentVersion(it) - } + }.toKeysVersionResult() // - Check the returned KeyBackupVersion is trusted val keysBackupVersionTrust = testHelper.doSync { 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 592b798bcc..ac83cb8882 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 @@ -22,16 +22,16 @@ import org.matrix.android.sdk.api.session.Session import org.matrix.android.sdk.api.session.crypto.keysbackup.KeysBackupService import org.matrix.android.sdk.api.session.crypto.keysbackup.KeysBackupState import org.matrix.android.sdk.api.session.crypto.keysbackup.KeysBackupStateListener +import org.matrix.android.sdk.api.session.crypto.keysbackup.KeysVersion +import org.matrix.android.sdk.api.session.crypto.keysbackup.MegolmBackupCreationInfo import org.matrix.android.sdk.common.CommonTestHelper import org.matrix.android.sdk.common.CryptoTestHelper import org.matrix.android.sdk.common.assertDictEquals import org.matrix.android.sdk.common.assertListEquals import org.matrix.android.sdk.internal.crypto.MegolmSessionData -import org.matrix.android.sdk.internal.crypto.keysbackup.model.MegolmBackupCreationInfo -import org.matrix.android.sdk.internal.crypto.keysbackup.model.rest.KeysVersion import java.util.concurrent.CountDownLatch -class KeysBackupTestHelper( +internal class KeysBackupTestHelper( private val testHelper: CommonTestHelper, private val cryptoTestHelper: CryptoTestHelper) { 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 6aefe98f86..31bd3c9cce 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 @@ -16,7 +16,7 @@ package org.matrix.android.sdk.internal.crypto.keysbackup -import org.matrix.android.sdk.internal.crypto.keysbackup.model.MegolmBackupCreationInfo +import org.matrix.android.sdk.api.session.crypto.keysbackup.MegolmBackupCreationInfo data class PrepareKeysBackupDataResult(val megolmBackupCreationInfo: MegolmBackupCreationInfo, val version: String) 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 67f17727b1..a882f69013 100644 --- a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/ssss/QuadSTests.kt +++ b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/ssss/QuadSTests.kt @@ -27,6 +27,7 @@ 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.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 @@ -37,11 +38,10 @@ 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.SessionTestParams import org.matrix.android.sdk.common.TestConstants -import org.matrix.android.sdk.internal.crypto.SSSS_ALGORITHM_AES_HMAC_SHA2 -import org.matrix.android.sdk.internal.crypto.crosssigning.toBase64NoPadding import org.matrix.android.sdk.internal.crypto.secrets.DefaultSharedSecretStorageService @RunWith(AndroidJUnit4::class) @@ -60,7 +60,7 @@ class QuadSTests : InstrumentedTest { val aliceSession = testHelper.createAccount(TestConstants.USER_ALICE, SessionTestParams(true)) - val quadS = aliceSession.sharedSecretStorageService + val quadS = aliceSession.sharedSecretStorageService() val TEST_KEY_ID = "my.test.Key" @@ -120,7 +120,7 @@ class QuadSTests : InstrumentedTest { // Store a secret val clearSecret = "42".toByteArray().toBase64NoPadding() testHelper.runBlockingTest { - aliceSession.sharedSecretStorageService.storeSecret( + aliceSession.sharedSecretStorageService().storeSecret( "secret.of.life", clearSecret, listOf(SharedSecretStorageService.KeyRef(null, keySpec)) // default key @@ -141,7 +141,7 @@ class QuadSTests : InstrumentedTest { // Try to decrypt?? val decryptedSecret = testHelper.runBlockingTest { - aliceSession.sharedSecretStorageService.getSecret( + aliceSession.sharedSecretStorageService().getSecret( "secret.of.life", null, // default key keySpec!! @@ -158,7 +158,7 @@ class QuadSTests : InstrumentedTest { val aliceSession = testHelper.createAccount(TestConstants.USER_ALICE, SessionTestParams(true)) - val quadS = aliceSession.sharedSecretStorageService + val quadS = aliceSession.sharedSecretStorageService() val TEST_KEY_ID = "my.test.Key" @@ -187,7 +187,7 @@ class QuadSTests : InstrumentedTest { val mySecretText = "Lorem ipsum dolor sit amet, consectetur adipiscing elit" testHelper.runBlockingTest { - aliceSession.sharedSecretStorageService.storeSecret( + aliceSession.sharedSecretStorageService().storeSecret( "my.secret", mySecretText.toByteArray().toBase64NoPadding(), listOf( @@ -207,14 +207,14 @@ class QuadSTests : InstrumentedTest { // Assert that can decrypt with both keys testHelper.runBlockingTest { - aliceSession.sharedSecretStorageService.getSecret("my.secret", + aliceSession.sharedSecretStorageService().getSecret("my.secret", keyId1, RawBytesKeySpec.fromRecoveryKey(key1Info.recoveryKey)!! ) } testHelper.runBlockingTest { - aliceSession.sharedSecretStorageService.getSecret("my.secret", + aliceSession.sharedSecretStorageService().getSecret("my.secret", keyId2, RawBytesKeySpec.fromRecoveryKey(key2Info.recoveryKey)!! ) @@ -236,7 +236,7 @@ class QuadSTests : InstrumentedTest { val mySecretText = "Lorem ipsum dolor sit amet, consectetur adipiscing elit" testHelper.runBlockingTest { - aliceSession.sharedSecretStorageService.storeSecret( + aliceSession.sharedSecretStorageService().storeSecret( "my.secret", mySecretText.toByteArray().toBase64NoPadding(), listOf(SharedSecretStorageService.KeyRef(keyId1, RawBytesKeySpec.fromRecoveryKey(key1Info.recoveryKey))) @@ -245,7 +245,7 @@ class QuadSTests : InstrumentedTest { testHelper.runBlockingTest { try { - aliceSession.sharedSecretStorageService.getSecret("my.secret", + aliceSession.sharedSecretStorageService().getSecret("my.secret", keyId1, RawBytesKeySpec.fromPassphrase( "A bad passphrase", @@ -260,7 +260,7 @@ class QuadSTests : InstrumentedTest { // Now try with correct key testHelper.runBlockingTest { - aliceSession.sharedSecretStorageService.getSecret("my.secret", + aliceSession.sharedSecretStorageService().getSecret("my.secret", keyId1, RawBytesKeySpec.fromPassphrase( passphrase, @@ -292,7 +292,7 @@ class QuadSTests : InstrumentedTest { } private fun generatedSecret(session: Session, keyId: String, asDefault: Boolean = true): SsssKeyCreationInfo { - val quadS = session.sharedSecretStorageService + val quadS = session.sharedSecretStorageService() val testHelper = CommonTestHelper(context()) val creationInfo = testHelper.runBlockingTest { @@ -312,7 +312,7 @@ class QuadSTests : InstrumentedTest { } private fun generatedSecretFromPassphrase(session: Session, passphrase: String, keyId: String, asDefault: Boolean = true): SsssKeyCreationInfo { - val quadS = session.sharedSecretStorageService + val quadS = session.sharedSecretStorageService() val testHelper = CommonTestHelper(context()) val creationInfo = testHelper.runBlockingTest { diff --git a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/verification/SASTest.kt b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/verification/SASTest.kt index 8cd725504d..14e659e2b6 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 @@ -31,6 +31,8 @@ import org.junit.runner.RunWith import org.junit.runners.MethodSorters import org.matrix.android.sdk.InstrumentedTest import org.matrix.android.sdk.api.session.Session +import org.matrix.android.sdk.api.session.crypto.model.CryptoDeviceInfo +import org.matrix.android.sdk.api.session.crypto.model.MXUsersDevicesMap import org.matrix.android.sdk.api.session.crypto.verification.CancelCode import org.matrix.android.sdk.api.session.crypto.verification.IncomingSasVerificationTransaction import org.matrix.android.sdk.api.session.crypto.verification.OutgoingSasVerificationTransaction @@ -44,8 +46,6 @@ 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.internal.crypto.model.CryptoDeviceInfo -import org.matrix.android.sdk.internal.crypto.model.MXUsersDevicesMap 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 diff --git a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/verification/qrcode/VerificationTest.kt b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/verification/qrcode/VerificationTest.kt index 2c96568102..374d709505 100644 --- a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/verification/qrcode/VerificationTest.kt +++ b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/verification/qrcode/VerificationTest.kt @@ -155,8 +155,8 @@ class VerificationTest : InstrumentedTest { bobSupportedMethods: List, expectedResultForAlice: ExpectedResult, expectedResultForBob: ExpectedResult) { - val testHelper = CommonTestHelper(context()) - val cryptoTestHelper = CryptoTestHelper(testHelper) + val testHelper = CommonTestHelper(context()) + val cryptoTestHelper = CryptoTestHelper(testHelper) val cryptoTestData = cryptoTestHelper.doE2ETestWithAliceAndBobInARoom() val aliceSession = cryptoTestData.firstSession diff --git a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/session/room/send/MarkdownParserTest.kt b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/session/room/send/MarkdownParserTest.kt index 1e3512a9df..ef98ed22c7 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 @@ -27,6 +27,7 @@ import org.junit.runner.RunWith import org.junit.runners.MethodSorters import org.matrix.android.sdk.InstrumentedTest import org.matrix.android.sdk.api.MatrixConfiguration +import org.matrix.android.sdk.api.util.TextContent import org.matrix.android.sdk.common.TestRoomDisplayNameFallbackProvider import org.matrix.android.sdk.internal.session.displayname.DisplayNameResolver import org.matrix.android.sdk.internal.session.room.send.pills.MentionLinkSpecComparator 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/internal/session/securestorage/SecretStoringUtilsTest.kt index 7ee6caed0d..6bcd12742b 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/internal/session/securestorage/SecretStoringUtilsTest.kt @@ -24,8 +24,8 @@ 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.internal.crypto.crosssigning.fromBase64 -import org.matrix.android.sdk.internal.crypto.crosssigning.toBase64NoPadding +import org.matrix.android.sdk.api.util.fromBase64 +import org.matrix.android.sdk.api.util.toBase64NoPadding import java.io.ByteArrayOutputStream import java.util.UUID diff --git a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/session/room/threads/ThreadMessagingTest.kt b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/session/room/threads/ThreadMessagingTest.kt index dcb181f0c1..8dac12e2df 100644 --- a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/session/room/threads/ThreadMessagingTest.kt +++ b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/session/room/threads/ThreadMessagingTest.kt @@ -31,6 +31,7 @@ import org.matrix.android.sdk.InstrumentedTest import org.matrix.android.sdk.api.session.events.model.getRootThreadEventId import org.matrix.android.sdk.api.session.events.model.isTextMessage import org.matrix.android.sdk.api.session.events.model.isThread +import org.matrix.android.sdk.api.session.getRoom import org.matrix.android.sdk.api.session.room.timeline.Timeline import org.matrix.android.sdk.api.session.room.timeline.TimelineSettings import org.matrix.android.sdk.common.CommonTestHelper diff --git a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/session/room/timeline/PollAggregationTest.kt b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/session/room/timeline/PollAggregationTest.kt index 6792d6ddfd..5700e6e040 100644 --- a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/session/room/timeline/PollAggregationTest.kt +++ b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/session/room/timeline/PollAggregationTest.kt @@ -30,6 +30,7 @@ import org.junit.runners.MethodSorters import org.matrix.android.sdk.InstrumentedTest import org.matrix.android.sdk.api.session.events.model.EventType import org.matrix.android.sdk.api.session.events.model.toModel +import org.matrix.android.sdk.api.session.getRoom import org.matrix.android.sdk.api.session.room.model.PollResponseAggregatedSummary import org.matrix.android.sdk.api.session.room.model.PollSummaryContent import org.matrix.android.sdk.api.session.room.model.message.MessagePollContent diff --git a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/session/room/timeline/TimelineForwardPaginationTest.kt b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/session/room/timeline/TimelineForwardPaginationTest.kt index ee44af58b3..b57afca502 100644 --- a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/session/room/timeline/TimelineForwardPaginationTest.kt +++ b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/session/room/timeline/TimelineForwardPaginationTest.kt @@ -30,6 +30,7 @@ import org.matrix.android.sdk.InstrumentedTest import org.matrix.android.sdk.api.extensions.orFalse import org.matrix.android.sdk.api.session.events.model.EventType import org.matrix.android.sdk.api.session.events.model.toModel +import org.matrix.android.sdk.api.session.getRoom import org.matrix.android.sdk.api.session.room.model.message.MessageContent import org.matrix.android.sdk.api.session.room.timeline.Timeline import org.matrix.android.sdk.api.session.room.timeline.TimelineSettings diff --git a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/session/room/timeline/TimelinePreviousLastForwardTest.kt b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/session/room/timeline/TimelinePreviousLastForwardTest.kt index c6d40bcaa2..3adc19b031 100644 --- a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/session/room/timeline/TimelinePreviousLastForwardTest.kt +++ b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/session/room/timeline/TimelinePreviousLastForwardTest.kt @@ -28,6 +28,7 @@ import org.matrix.android.sdk.InstrumentedTest import org.matrix.android.sdk.api.extensions.orFalse import org.matrix.android.sdk.api.session.events.model.EventType import org.matrix.android.sdk.api.session.events.model.toModel +import org.matrix.android.sdk.api.session.getRoom import org.matrix.android.sdk.api.session.room.model.message.MessageContent import org.matrix.android.sdk.api.session.room.timeline.Timeline import org.matrix.android.sdk.api.session.room.timeline.TimelineSettings diff --git a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/session/room/timeline/TimelineSimpleBackPaginationTest.kt b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/session/room/timeline/TimelineSimpleBackPaginationTest.kt index 53f76f1c46..59546f244e 100644 --- a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/session/room/timeline/TimelineSimpleBackPaginationTest.kt +++ b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/session/room/timeline/TimelineSimpleBackPaginationTest.kt @@ -28,6 +28,7 @@ import org.matrix.android.sdk.InstrumentedTest import org.matrix.android.sdk.api.extensions.orFalse import org.matrix.android.sdk.api.session.events.model.isTextMessage import org.matrix.android.sdk.api.session.events.model.toModel +import org.matrix.android.sdk.api.session.getRoom import org.matrix.android.sdk.api.session.room.model.message.MessageTextContent import org.matrix.android.sdk.api.session.room.timeline.Timeline import org.matrix.android.sdk.api.session.room.timeline.TimelineSettings diff --git a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/session/room/timeline/TimelineWithManyMembersTest.kt b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/session/room/timeline/TimelineWithManyMembersTest.kt index ce02b2b527..3e87d68f22 100644 --- a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/session/room/timeline/TimelineWithManyMembersTest.kt +++ b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/session/room/timeline/TimelineWithManyMembersTest.kt @@ -27,6 +27,7 @@ import org.junit.runners.MethodSorters import org.matrix.android.sdk.InstrumentedTest import org.matrix.android.sdk.api.extensions.orFalse import org.matrix.android.sdk.api.session.events.model.toModel +import org.matrix.android.sdk.api.session.getRoom import org.matrix.android.sdk.api.session.room.model.message.MessageContent import org.matrix.android.sdk.api.session.room.timeline.TimelineSettings import org.matrix.android.sdk.common.CommonTestHelper diff --git a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/session/search/SearchMessagesTest.kt b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/session/search/SearchMessagesTest.kt index fa07cf5a02..ab0bbe7f73 100644 --- a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/session/search/SearchMessagesTest.kt +++ b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/session/search/SearchMessagesTest.kt @@ -24,6 +24,7 @@ import org.junit.runners.JUnit4 import org.junit.runners.MethodSorters import org.matrix.android.sdk.InstrumentedTest import org.matrix.android.sdk.api.extensions.orFalse +import org.matrix.android.sdk.api.session.getRoom import org.matrix.android.sdk.api.session.search.SearchResult import org.matrix.android.sdk.common.CommonTestHelper import org.matrix.android.sdk.common.CryptoTestData @@ -59,9 +60,10 @@ class SearchMessagesTest : InstrumentedTest { fun sendTextMessageAndSearchPartOfItUsingRoom() { doTest { cryptoTestData -> cryptoTestData.firstSession - .getRoom(cryptoTestData.roomId)!! + .searchService() .search( searchTerm = "lore", + roomId = cryptoTestData.roomId, limit = 10, includeProfile = true, afterLimit = 0, diff --git a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/session/space/SpaceCreationTest.kt b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/session/space/SpaceCreationTest.kt index 3b0f7586cc..906f724bbe 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 @@ -147,7 +147,7 @@ class SpaceCreationTest : InstrumentedTest { // create a room var firstChild: String? = null commonTestHelper.waitWithLatch { - firstChild = aliceSession.createRoom(CreateRoomParams().apply { + firstChild = aliceSession.roomService().createRoom(CreateRoomParams().apply { this.name = "FirstRoom" this.topic = "Description of first room" this.preset = CreateRoomPreset.PRESET_PUBLIC_CHAT @@ -162,7 +162,7 @@ class SpaceCreationTest : InstrumentedTest { var secondChild: String? = null commonTestHelper.waitWithLatch { - secondChild = aliceSession.createRoom(CreateRoomParams().apply { + secondChild = aliceSession.roomService().createRoom(CreateRoomParams().apply { this.name = "SecondRoom" this.topic = "Description of second room" this.preset = CreateRoomPreset.PRESET_PUBLIC_CHAT diff --git a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/session/space/SpaceHierarchyTest.kt b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/session/space/SpaceHierarchyTest.kt index 20faa81bb6..27a11e3c02 100644 --- a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/session/space/SpaceHierarchyTest.kt +++ b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/session/space/SpaceHierarchyTest.kt @@ -35,6 +35,8 @@ import org.matrix.android.sdk.api.session.Session import org.matrix.android.sdk.api.session.events.model.EventType import org.matrix.android.sdk.api.session.events.model.toContent import org.matrix.android.sdk.api.session.events.model.toModel +import org.matrix.android.sdk.api.session.getRoom +import org.matrix.android.sdk.api.session.getRoomSummary import org.matrix.android.sdk.api.session.room.model.PowerLevelsContent import org.matrix.android.sdk.api.session.room.model.RoomJoinRulesAllowEntry import org.matrix.android.sdk.api.session.room.model.RoomSummary @@ -68,7 +70,7 @@ class SpaceHierarchyTest : InstrumentedTest { var roomId = "" commonTestHelper.waitWithLatch { - roomId = session.createRoom(CreateRoomParams().apply { name = "General" }) + roomId = session.roomService().createRoom(CreateRoomParams().apply { name = "General" }) it.countDown() } @@ -203,27 +205,27 @@ class SpaceHierarchyTest : InstrumentedTest { var orphan1 = "" commonTestHelper.waitWithLatch { - orphan1 = session.createRoom(CreateRoomParams().apply { name = "O1" }) + orphan1 = session.roomService().createRoom(CreateRoomParams().apply { name = "O1" }) it.countDown() } var orphan2 = "" commonTestHelper.waitWithLatch { - orphan2 = session.createRoom(CreateRoomParams().apply { name = "O2" }) + orphan2 = session.roomService().createRoom(CreateRoomParams().apply { name = "O2" }) it.countDown() } - val allRooms = session.getRoomSummaries(roomSummaryQueryParams { excludeType = listOf(RoomType.SPACE) }) + val allRooms = session.roomService().getRoomSummaries(roomSummaryQueryParams { excludeType = listOf(RoomType.SPACE) }) assertEquals("Unexpected number of rooms", 9, allRooms.size) - val orphans = session.getFlattenRoomSummaryChildrenOf(null) + val orphans = session.roomService().getFlattenRoomSummaryChildrenOf(null) assertEquals("Unexpected number of orphan rooms", 2, orphans.size) assertTrue("O1 should be an orphan", orphans.any { it.roomId == orphan1 }) assertTrue("O2 should be an orphan ${orphans.map { it.name }}", orphans.any { it.roomId == orphan2 }) - val aChildren = session.getFlattenRoomSummaryChildrenOf(spaceAInfo.spaceId) + val aChildren = session.roomService().getFlattenRoomSummaryChildrenOf(spaceAInfo.spaceId) assertEquals("Unexpected number of flatten child rooms", 4, aChildren.size) assertTrue("A1 should be a child of A", aChildren.any { it.name == "A1" }) @@ -233,13 +235,13 @@ class SpaceHierarchyTest : InstrumentedTest { // Add a non canonical child and check that it does not appear as orphan commonTestHelper.waitWithLatch { - val a3 = session.createRoom(CreateRoomParams().apply { name = "A3" }) + val a3 = session.roomService().createRoom(CreateRoomParams().apply { name = "A3" }) spaceA!!.addChildren(a3, viaServers, null, false) it.countDown() } Thread.sleep(6_000) - val orphansUpdate = session.getRoomSummaries(roomSummaryQueryParams { + val orphansUpdate = session.roomService().getRoomSummaries(roomSummaryQueryParams { activeSpaceFilter = ActiveSpaceFilter.ActiveSpace(null) }) assertEquals("Unexpected number of orphan rooms ${orphansUpdate.map { it.name }}", 2, orphansUpdate.size) @@ -279,7 +281,7 @@ class SpaceHierarchyTest : InstrumentedTest { // A -> C -> A - val aChildren = session.getFlattenRoomSummaryChildrenOf(spaceAInfo.spaceId) + val aChildren = session.roomService().getFlattenRoomSummaryChildrenOf(spaceAInfo.spaceId) assertEquals("Unexpected number of flatten child rooms ${aChildren.map { it.name }}", 4, aChildren.size) assertTrue("A1 should be a child of A", aChildren.any { it.name == "A1" }) @@ -319,7 +321,7 @@ class SpaceHierarchyTest : InstrumentedTest { commonTestHelper.waitWithLatch { latch -> - val flatAChildren = session.getFlattenRoomSummaryChildrenOfLive(spaceAInfo.spaceId) + val flatAChildren = session.roomService().getFlattenRoomSummaryChildrenOfLive(spaceAInfo.spaceId) val childObserver = object : Observer> { override fun onChanged(children: List?) { // Log.d("## TEST", "Space A flat children update : ${children?.map { it.name }}") @@ -346,7 +348,7 @@ class SpaceHierarchyTest : InstrumentedTest { val bRoomId = spaceBInfo.roomIds.first() commonTestHelper.waitWithLatch { latch -> - val flatAChildren = session.getFlattenRoomSummaryChildrenOfLive(spaceAInfo.spaceId) + val flatAChildren = session.roomService().getFlattenRoomSummaryChildrenOfLive(spaceAInfo.spaceId) val childObserver = object : Observer> { override fun onChanged(children: List?) { System.out.println("## TEST | Space A flat children update : ${children?.map { it.name }}") @@ -359,7 +361,7 @@ class SpaceHierarchyTest : InstrumentedTest { } // part from b room - session.leaveRoom(bRoomId) + session.roomService().leaveRoom(bRoomId) // The room should have disapear from flat children flatAChildren.observeForever(childObserver) } @@ -385,7 +387,7 @@ class SpaceHierarchyTest : InstrumentedTest { val viaServers = listOf(session.sessionParams.homeServerHost ?: "") roomIds = childInfo.map { entry -> - session.createRoom(CreateRoomParams().apply { name = entry.first }) + session.roomService().createRoom(CreateRoomParams().apply { name = entry.first }) } roomIds.forEachIndexed { index, roomId -> syncedSpace!!.addChildren(roomId, viaServers, null, childInfo[index].second) @@ -414,8 +416,9 @@ class SpaceHierarchyTest : InstrumentedTest { roomIds = childInfo.map { entry -> val homeServerCapabilities = session + .homeServerCapabilitiesService() .getHomeServerCapabilities() - session.createRoom(CreateRoomParams().apply { + session.roomService().createRoom(CreateRoomParams().apply { name = entry.first this.featurePreset = RestrictedRoomPreset( homeServerCapabilities, @@ -475,7 +478,9 @@ class SpaceHierarchyTest : InstrumentedTest { // + C // + c1, c2 - val rootSpaces = session.spaceService().getRootSpaceSummaries() + val rootSpaces = commonTestHelper.runBlockingTest { + session.spaceService().getRootSpaceSummaries() + } assertEquals("Unexpected number of root spaces ${rootSpaces.map { it.name }}", 2, rootSpaces.size) @@ -498,18 +503,18 @@ class SpaceHierarchyTest : InstrumentedTest { } commonTestHelper.runBlockingTest { - bobSession.joinRoom(spaceAInfo.spaceId, null, emptyList()) + bobSession.roomService().joinRoom(spaceAInfo.spaceId, null, emptyList()) } var bobRoomId = "" commonTestHelper.waitWithLatch { - bobRoomId = bobSession.createRoom(CreateRoomParams().apply { name = "A Bob Room" }) + bobRoomId = bobSession.roomService().createRoom(CreateRoomParams().apply { name = "A Bob Room" }) bobSession.getRoom(bobRoomId)!!.invite(aliceSession.myUserId) it.countDown() } commonTestHelper.runBlockingTest { - aliceSession.joinRoom(bobRoomId) + aliceSession.roomService().joinRoom(bobRoomId) } commonTestHelper.waitWithLatch { latch -> diff --git a/matrix-sdk-android/src/debug/java/org/matrix/android/sdk/internal/network/interceptors/CurlLoggingInterceptor.kt b/matrix-sdk-android/src/debug/java/org/matrix/android/sdk/internal/network/interceptors/CurlLoggingInterceptor.kt index 3add757efa..6dd3553d02 100644 --- a/matrix-sdk-android/src/debug/java/org/matrix/android/sdk/internal/network/interceptors/CurlLoggingInterceptor.kt +++ b/matrix-sdk-android/src/debug/java/org/matrix/android/sdk/internal/network/interceptors/CurlLoggingInterceptor.kt @@ -37,7 +37,7 @@ import javax.inject.Inject */ @MatrixScope internal class CurlLoggingInterceptor @Inject constructor() : - Interceptor { + Interceptor { /** * Set any additional curl command options (see 'curl --help'). diff --git a/matrix-sdk-android/src/debug/java/org/matrix/android/sdk/internal/network/interceptors/FormattedJsonHttpLogger.kt b/matrix-sdk-android/src/debug/java/org/matrix/android/sdk/internal/network/interceptors/FormattedJsonHttpLogger.kt index 34ed28d467..2661bd1f70 100644 --- a/matrix-sdk-android/src/debug/java/org/matrix/android/sdk/internal/network/interceptors/FormattedJsonHttpLogger.kt +++ b/matrix-sdk-android/src/debug/java/org/matrix/android/sdk/internal/network/interceptors/FormattedJsonHttpLogger.kt @@ -23,7 +23,7 @@ import org.json.JSONException import org.json.JSONObject import timber.log.Timber -class FormattedJsonHttpLogger : HttpLoggingInterceptor.Logger { +internal class FormattedJsonHttpLogger : HttpLoggingInterceptor.Logger { companion object { private const val INDENT_SPACE = 2 diff --git a/matrix-sdk-android/src/main/java/org/commonmark/ext/maths/DisplayMaths.kt b/matrix-sdk-android/src/main/java/org/commonmark/ext/maths/DisplayMaths.kt index b8ee36e724..3e83594ca3 100644 --- a/matrix-sdk-android/src/main/java/org/commonmark/ext/maths/DisplayMaths.kt +++ b/matrix-sdk-android/src/main/java/org/commonmark/ext/maths/DisplayMaths.kt @@ -17,7 +17,7 @@ package org.commonmark.ext.maths import org.commonmark.node.CustomBlock -class DisplayMaths(private val delimiter: DisplayDelimiter) : CustomBlock() { +internal class DisplayMaths(private val delimiter: DisplayDelimiter) : CustomBlock() { enum class DisplayDelimiter { DOUBLE_DOLLAR, SQUARE_BRACKET_ESCAPED 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 962b1b8cbf..3fe8d15696 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 @@ -18,7 +18,7 @@ package org.commonmark.ext.maths import org.commonmark.node.CustomNode import org.commonmark.node.Delimited -class InlineMaths(private val delimiter: InlineDelimiter) : CustomNode(), Delimited { +internal class InlineMaths(private val delimiter: InlineDelimiter) : CustomNode(), Delimited { enum class InlineDelimiter { SINGLE_DOLLAR, ROUND_BRACKET_ESCAPED diff --git a/matrix-sdk-android/src/main/java/org/commonmark/ext/maths/MathsExtension.kt b/matrix-sdk-android/src/main/java/org/commonmark/ext/maths/MathsExtension.kt index 18c0fc4284..7a53253bd6 100644 --- a/matrix-sdk-android/src/main/java/org/commonmark/ext/maths/MathsExtension.kt +++ b/matrix-sdk-android/src/main/java/org/commonmark/ext/maths/MathsExtension.kt @@ -21,7 +21,7 @@ import org.commonmark.ext.maths.internal.MathsHtmlNodeRenderer import org.commonmark.parser.Parser import org.commonmark.renderer.html.HtmlRenderer -class MathsExtension private constructor() : Parser.ParserExtension, HtmlRenderer.HtmlRendererExtension { +internal class MathsExtension private constructor() : Parser.ParserExtension, HtmlRenderer.HtmlRendererExtension { override fun extend(parserBuilder: Parser.Builder) { parserBuilder.customDelimiterProcessor(DollarMathsDelimiterProcessor()) } diff --git a/matrix-sdk-android/src/main/java/org/commonmark/ext/maths/internal/DollarMathsDelimiterProcessor.kt b/matrix-sdk-android/src/main/java/org/commonmark/ext/maths/internal/DollarMathsDelimiterProcessor.kt index cfd03fa8f1..95ea1a1766 100644 --- a/matrix-sdk-android/src/main/java/org/commonmark/ext/maths/internal/DollarMathsDelimiterProcessor.kt +++ b/matrix-sdk-android/src/main/java/org/commonmark/ext/maths/internal/DollarMathsDelimiterProcessor.kt @@ -21,7 +21,7 @@ import org.commonmark.node.Text import org.commonmark.parser.delimiter.DelimiterProcessor import org.commonmark.parser.delimiter.DelimiterRun -class DollarMathsDelimiterProcessor : DelimiterProcessor { +internal class DollarMathsDelimiterProcessor : DelimiterProcessor { override fun getOpeningCharacter() = '$' override fun getClosingCharacter() = '$' diff --git a/matrix-sdk-android/src/main/java/org/commonmark/ext/maths/internal/MathsHtmlNodeRenderer.kt b/matrix-sdk-android/src/main/java/org/commonmark/ext/maths/internal/MathsHtmlNodeRenderer.kt index 94652ed7ad..0efecbbe8a 100644 --- a/matrix-sdk-android/src/main/java/org/commonmark/ext/maths/internal/MathsHtmlNodeRenderer.kt +++ b/matrix-sdk-android/src/main/java/org/commonmark/ext/maths/internal/MathsHtmlNodeRenderer.kt @@ -22,7 +22,7 @@ import org.commonmark.renderer.html.HtmlNodeRendererContext import org.commonmark.renderer.html.HtmlWriter import java.util.Collections -class MathsHtmlNodeRenderer(private val context: HtmlNodeRendererContext) : MathsNodeRenderer() { +internal class MathsHtmlNodeRenderer(private val context: HtmlNodeRendererContext) : MathsNodeRenderer() { private val html: HtmlWriter = context.writer override fun render(node: Node) { val display = node.javaClass == DisplayMaths::class.java diff --git a/matrix-sdk-android/src/main/java/org/commonmark/ext/maths/internal/MathsNodeRenderer.kt b/matrix-sdk-android/src/main/java/org/commonmark/ext/maths/internal/MathsNodeRenderer.kt index 55cdc05c39..6924a9fb54 100644 --- a/matrix-sdk-android/src/main/java/org/commonmark/ext/maths/internal/MathsNodeRenderer.kt +++ b/matrix-sdk-android/src/main/java/org/commonmark/ext/maths/internal/MathsNodeRenderer.kt @@ -19,9 +19,8 @@ import org.commonmark.ext.maths.DisplayMaths import org.commonmark.ext.maths.InlineMaths import org.commonmark.node.Node import org.commonmark.renderer.NodeRenderer -import java.util.HashSet -abstract class MathsNodeRenderer : NodeRenderer { +internal abstract class MathsNodeRenderer : NodeRenderer { override fun getNodeTypes(): Set> { val types: MutableSet> = HashSet() types.add(InlineMaths::class.java) 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 5fedff53f0..217f7e3da8 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/Matrix.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/Matrix.kt @@ -29,6 +29,7 @@ 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.settings.LightweightSettingsStorage import org.matrix.android.sdk.internal.SessionManager import org.matrix.android.sdk.internal.di.DaggerMatrixComponent import org.matrix.android.sdk.internal.network.ApiInterceptor @@ -42,7 +43,8 @@ import javax.inject.Inject /** * This is the main entry point to the matrix sdk. - * To get the singleton instance, use getInstance static method. + *
+ * See [Companion.createInstance] to create an instance. The app should create and manage the instance itself. */ class Matrix private constructor(context: Context, matrixConfiguration: MatrixConfiguration) { @@ -56,6 +58,7 @@ class Matrix private constructor(context: Context, matrixConfiguration: MatrixCo @Inject internal lateinit var homeServerHistoryService: HomeServerHistoryService @Inject internal lateinit var apiInterceptor: ApiInterceptor @Inject internal lateinit var matrixWorkerFactory: MatrixWorkerFactory + @Inject internal lateinit var lightweightSettingsStorage: LightweightSettingsStorage init { Monarchy.init(context) @@ -72,17 +75,15 @@ class Matrix private constructor(context: Context, matrixConfiguration: MatrixCo fun getUserAgent() = userAgentHolder.userAgent - fun authenticationService(): AuthenticationService { - return authenticationService - } + fun authenticationService() = authenticationService fun rawService() = rawService + fun lightweightSettingsStorage() = lightweightSettingsStorage + fun homeServerHistoryService() = homeServerHistoryService - fun legacySessionImporter(): LegacySessionImporter { - return legacySessionImporter - } + fun legacySessionImporter() = legacySessionImporter fun workerFactory(): WorkerFactory = matrixWorkerFactory @@ -139,6 +140,9 @@ class Matrix private constructor(context: Context, matrixConfiguration: MatrixCo return instance } + /** + * @return a String with details about the Matrix SDK version + */ fun getSdkVersion(): String { return BuildConfig.SDK_VERSION + " (" + BuildConfig.GIT_SDK_REVISION + ")" } diff --git a/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 a97e7d8cbe..f8472319fd 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 @@ -62,9 +62,9 @@ data class MatrixConfiguration( */ val roomDisplayNameFallbackProvider: RoomDisplayNameFallbackProvider, /** - * True to enable presence information sync (if available). False to disable regardless of server setting. + * Thread messages default enable/disabled value */ - val presenceSyncEnabled: Boolean = true + val threadMessagesEnabledDefault: Boolean = false, ) { /** diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/auth/HomeServerHistoryService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/auth/HomeServerHistoryService.kt index 77e33b8934..6850bdd444 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/auth/HomeServerHistoryService.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/auth/HomeServerHistoryService.kt @@ -20,10 +20,19 @@ package org.matrix.android.sdk.api.auth * A simple service to remember homeservers you already connected to. */ interface HomeServerHistoryService { - + /** + * Get a list of stored homeserver urls. + */ fun getKnownServersUrls(): List + /** + * Add a homeserver url to the list of stored homeserver urls. + * Will not be added again if already present in the list. + */ fun addHomeServerToHistory(url: String) + /** + * Delete the list of stored homeserver urls. + */ fun clearHistory() } diff --git a/vector/src/main/java/im/vector/app/features/login/terms/UrlAndName.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/auth/UrlAndName.kt similarity index 87% rename from vector/src/main/java/im/vector/app/features/login/terms/UrlAndName.kt rename to matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/auth/UrlAndName.kt index f298aeca37..ca0123b832 100644 --- a/vector/src/main/java/im/vector/app/features/login/terms/UrlAndName.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/auth/UrlAndName.kt @@ -1,5 +1,5 @@ /* - * Copyright 2019 New Vector Ltd + * Copyright 2020 The Matrix.org Foundation C.I.C. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -14,7 +14,7 @@ * limitations under the License. */ -package im.vector.app.features.login.terms +package org.matrix.android.sdk.api.auth data class UrlAndName( val url: String, diff --git a/vector/src/main/java/im/vector/app/features/login/terms/converter.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/auth/converter.kt similarity index 70% rename from vector/src/main/java/im/vector/app/features/login/terms/converter.kt rename to matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/auth/converter.kt index f054a679bc..1a4c1ee51c 100644 --- a/vector/src/main/java/im/vector/app/features/login/terms/converter.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/auth/converter.kt @@ -1,120 +1,127 @@ -/* - * Copyright 2019 New Vector Ltd - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package im.vector.app.features.login.terms - -import org.matrix.android.sdk.api.auth.registration.TermPolicies -import org.matrix.android.sdk.internal.auth.registration.LocalizedFlowDataLoginTerms - -/** - * This method extract the policies from the login terms parameter, regarding the user language. - * For each policy, if user language is not found, the default language is used and if not found, the first url and name are used (not predictable) - * - * Example of Data: - *
- * "m.login.terms": {
- *       "policies": {
- *         "privacy_policy": {
- *           "version": "1.0",
- *           "en": {
- *             "url": "http:\/\/matrix.org\/_matrix\/consent?v=1.0",
- *             "name": "Terms and Conditions"
- *           }
- *         }
- *       }
- *     }
- *
- * - * @param userLanguage the user language - * @param defaultLanguage the default language to use if the user language is not found for a policy in registrationFlowResponse - */ -fun TermPolicies.toLocalizedLoginTerms(userLanguage: String, - defaultLanguage: String = "en"): List { - val result = ArrayList() - - val policies = get("policies") - if (policies is Map<*, *>) { - policies.keys.forEach { policyName -> - val localizedFlowDataLoginTerms = LocalizedFlowDataLoginTerms() - localizedFlowDataLoginTerms.policyName = policyName as String - - val policy = policies[policyName] - - // Enter this policy - if (policy is Map<*, *>) { - // Version - localizedFlowDataLoginTerms.version = policy["version"] as String? - - var userLanguageUrlAndName: UrlAndName? = null - var defaultLanguageUrlAndName: UrlAndName? = null - var firstUrlAndName: UrlAndName? = null - - // Search for language - policy.keys.forEach { policyKey -> - when (policyKey) { - "version" -> Unit // Ignore - userLanguage -> { - // We found the data for the user language - userLanguageUrlAndName = extractUrlAndName(policy[policyKey]) - } - defaultLanguage -> { - // We found default language - defaultLanguageUrlAndName = extractUrlAndName(policy[policyKey]) - } - else -> { - if (firstUrlAndName == null) { - // Get at least some data - firstUrlAndName = extractUrlAndName(policy[policyKey]) - } - } - } - } - - // Copy found language data by priority - when { - userLanguageUrlAndName != null -> { - localizedFlowDataLoginTerms.localizedUrl = userLanguageUrlAndName!!.url - localizedFlowDataLoginTerms.localizedName = userLanguageUrlAndName!!.name - } - defaultLanguageUrlAndName != null -> { - localizedFlowDataLoginTerms.localizedUrl = defaultLanguageUrlAndName!!.url - localizedFlowDataLoginTerms.localizedName = defaultLanguageUrlAndName!!.name - } - firstUrlAndName != null -> { - localizedFlowDataLoginTerms.localizedUrl = firstUrlAndName!!.url - localizedFlowDataLoginTerms.localizedName = firstUrlAndName!!.name - } - } - } - - result.add(localizedFlowDataLoginTerms) - } - } - - return result -} - -private fun extractUrlAndName(policyData: Any?): UrlAndName? { - if (policyData is Map<*, *>) { - val url = policyData["url"] as String? - val name = policyData["name"] as String? - - if (url != null && name != null) { - return UrlAndName(url, name) - } - } - return null -} +/* + * Copyright 2020 The Matrix.org Foundation C.I.C. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.matrix.android.sdk.api.auth + +import org.matrix.android.sdk.api.auth.data.LocalizedFlowDataLoginTerms +import org.matrix.android.sdk.api.auth.registration.TermPolicies + +/** + * This method extract the policies from the login terms parameter, regarding the user language. + * For each policy, if user language is not found, the default language is used and if not found, the first url and name are used (not predictable) + * + * Example of Data: + *
+ * "m.login.terms": {
+ *       "policies": {
+ *         "privacy_policy": {
+ *           "version": "1.0",
+ *           "en": {
+ *             "url": "http:\/\/matrix.org\/_matrix\/consent?v=1.0",
+ *             "name": "Terms and Conditions"
+ *           }
+ *         }
+ *       }
+ *     }
+ *
+ * + * @param userLanguage the user language + * @param defaultLanguage the default language to use if the user language is not found for a policy in registrationFlowResponse + */ +fun TermPolicies.toLocalizedLoginTerms(userLanguage: String, + defaultLanguage: String = "en"): List { + val result = ArrayList() + + val policies = get("policies") + if (policies is Map<*, *>) { + policies.keys.forEach { policyName -> + val localizedFlowDataLoginTermsPolicyName = policyName as String + var localizedFlowDataLoginTermsVersion: String? = null + var localizedFlowDataLoginTermsLocalizedUrl: String? = null + var localizedFlowDataLoginTermsLocalizedName: String? = null + + val policy = policies[policyName] + + // Enter this policy + if (policy is Map<*, *>) { + // Version + localizedFlowDataLoginTermsVersion = policy["version"] as String? + + var userLanguageUrlAndName: UrlAndName? = null + var defaultLanguageUrlAndName: UrlAndName? = null + var firstUrlAndName: UrlAndName? = null + + // Search for language + policy.keys.forEach { policyKey -> + when (policyKey) { + "version" -> Unit // Ignore + userLanguage -> { + // We found the data for the user language + userLanguageUrlAndName = extractUrlAndName(policy[policyKey]) + } + defaultLanguage -> { + // We found default language + defaultLanguageUrlAndName = extractUrlAndName(policy[policyKey]) + } + else -> { + if (firstUrlAndName == null) { + // Get at least some data + firstUrlAndName = extractUrlAndName(policy[policyKey]) + } + } + } + } + + // Copy found language data by priority + when { + userLanguageUrlAndName != null -> { + localizedFlowDataLoginTermsLocalizedUrl = userLanguageUrlAndName!!.url + localizedFlowDataLoginTermsLocalizedName = userLanguageUrlAndName!!.name + } + defaultLanguageUrlAndName != null -> { + localizedFlowDataLoginTermsLocalizedUrl = defaultLanguageUrlAndName!!.url + localizedFlowDataLoginTermsLocalizedName = defaultLanguageUrlAndName!!.name + } + firstUrlAndName != null -> { + localizedFlowDataLoginTermsLocalizedUrl = firstUrlAndName!!.url + localizedFlowDataLoginTermsLocalizedName = firstUrlAndName!!.name + } + } + } + + result.add(LocalizedFlowDataLoginTerms( + policyName = localizedFlowDataLoginTermsPolicyName, + version = localizedFlowDataLoginTermsVersion, + localizedUrl = localizedFlowDataLoginTermsLocalizedUrl, + localizedName = localizedFlowDataLoginTermsLocalizedName + )) + } + } + + return result +} + +private fun extractUrlAndName(policyData: Any?): UrlAndName? { + if (policyData is Map<*, *>) { + val url = policyData["url"] as String? + val name = policyData["name"] as String? + + if (url != null && name != null) { + return UrlAndName(url, name) + } + } + return null +} diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/auth/data/Credentials.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/auth/data/Credentials.kt index 434e4a6e2e..317acccfb5 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/auth/data/Credentials.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/auth/data/Credentials.kt @@ -18,7 +18,7 @@ package org.matrix.android.sdk.api.auth.data import com.squareup.moshi.Json import com.squareup.moshi.JsonClass -import org.matrix.android.sdk.internal.util.md5 +import org.matrix.android.sdk.api.util.md5 /** * This data class hold credentials user data. diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/auth/data/HomeServerConnectionConfig.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/auth/data/HomeServerConnectionConfig.kt index e87824d6a5..c2c1f043bb 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/auth/data/HomeServerConnectionConfig.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/auth/data/HomeServerConnectionConfig.kt @@ -22,7 +22,7 @@ import okhttp3.CipherSuite import okhttp3.ConnectionSpec import okhttp3.TlsVersion import org.matrix.android.sdk.api.auth.data.HomeServerConnectionConfig.Builder -import org.matrix.android.sdk.internal.network.ssl.Fingerprint +import org.matrix.android.sdk.api.network.ssl.Fingerprint import org.matrix.android.sdk.internal.util.ensureTrailingSlash /** diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/registration/LocalizedFlowDataLoginTerms.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/auth/data/LocalizedFlowDataLoginTerms.kt similarity index 79% rename from matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/registration/LocalizedFlowDataLoginTerms.kt rename to matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/auth/data/LocalizedFlowDataLoginTerms.kt index 5d119bb617..1e844a1d94 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/registration/LocalizedFlowDataLoginTerms.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/auth/data/LocalizedFlowDataLoginTerms.kt @@ -14,7 +14,7 @@ * limitations under the License. */ -package org.matrix.android.sdk.internal.auth.registration +package org.matrix.android.sdk.api.auth.data import android.os.Parcelable import kotlinx.parcelize.Parcelize @@ -24,8 +24,8 @@ import kotlinx.parcelize.Parcelize */ @Parcelize data class LocalizedFlowDataLoginTerms( - var policyName: String? = null, - var version: String? = null, - var localizedUrl: String? = null, - var localizedName: String? = null + val policyName: String?, + val version: String?, + val localizedUrl: String?, + val localizedName: String? ) : Parcelable 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 ac740ddab7..0da9eb4b7e 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 @@ -19,8 +19,8 @@ package org.matrix.android.sdk.api.auth.registration import com.squareup.moshi.Json import com.squareup.moshi.JsonClass import org.matrix.android.sdk.api.auth.data.LoginFlowTypes +import org.matrix.android.sdk.api.session.uia.InteractiveAuthenticationFlow import org.matrix.android.sdk.api.util.JsonDict -import org.matrix.android.sdk.internal.auth.data.InteractiveAuthenticationFlow @JsonClass(generateAdapter = true) data class RegistrationFlowResponse( diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/auth/registration/RegistrationWizard.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/auth/registration/RegistrationWizard.kt index 621253faa5..0cda64499f 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/auth/registration/RegistrationWizard.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/auth/registration/RegistrationWizard.kt @@ -16,6 +16,8 @@ package org.matrix.android.sdk.api.auth.registration +import org.matrix.android.sdk.api.util.JsonDict + /** * Set of methods to be able to create an account on a homeserver. * @@ -73,6 +75,13 @@ interface RegistrationWizard { */ suspend fun dummy(): RegistrationResult + /** + * Perform custom registration stage by sending a custom JsonDict. + * Current registration "session" param will be included into authParams by default. + * The authParams should contain at least one entry "type" with a String value. + */ + suspend fun registrationCustom(authParams: JsonDict): RegistrationResult + /** * Perform the "m.login.email.identity" or "m.login.msisdn" stage. * diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/CryptoConstants.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/crypto/CryptoConstants.kt similarity index 96% rename from matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/CryptoConstants.kt rename to matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/crypto/CryptoConstants.kt index 96635b33d6..172cfa8360 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/CryptoConstants.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/crypto/CryptoConstants.kt @@ -14,7 +14,7 @@ * limitations under the License. */ -package org.matrix.android.sdk.internal.crypto +package org.matrix.android.sdk.api.crypto /** * Matrix algorithm value for olm. diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/extensions/MatrixSdkExtensions.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/extensions/MatrixSdkExtensions.kt index 1cdb6d49a5..f09e9bb340 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/extensions/MatrixSdkExtensions.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/extensions/MatrixSdkExtensions.kt @@ -16,8 +16,8 @@ package org.matrix.android.sdk.api.extensions -import org.matrix.android.sdk.internal.crypto.model.CryptoDeviceInfo -import org.matrix.android.sdk.internal.crypto.model.rest.DeviceInfo +import org.matrix.android.sdk.api.session.crypto.model.CryptoDeviceInfo +import org.matrix.android.sdk.api.session.crypto.model.DeviceInfo /* ========================================================================================== * MXDeviceInfo diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/failure/Extensions.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/failure/Extensions.kt index aabe6e0d06..362ebcec26 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/failure/Extensions.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/failure/Extensions.kt @@ -22,29 +22,29 @@ import org.matrix.android.sdk.api.session.contentscanner.ContentScannerError import org.matrix.android.sdk.api.session.contentscanner.ScanFailure import org.matrix.android.sdk.internal.di.MoshiProvider import java.io.IOException +import java.net.UnknownHostException import javax.net.ssl.HttpsURLConnection -fun Throwable.is401() = - this is Failure.ServerError && - httpCode == HttpsURLConnection.HTTP_UNAUTHORIZED && /* 401 */ - error.code == MatrixError.M_UNAUTHORIZED +fun Throwable.is401() = this is Failure.ServerError && + httpCode == HttpsURLConnection.HTTP_UNAUTHORIZED && /* 401 */ + error.code == MatrixError.M_UNAUTHORIZED -fun Throwable.isTokenError() = - this is Failure.ServerError && - (error.code == MatrixError.M_UNKNOWN_TOKEN || - error.code == MatrixError.M_MISSING_TOKEN || - error.code == MatrixError.ORG_MATRIX_EXPIRED_ACCOUNT) +fun Throwable.is404() = this is Failure.ServerError && + httpCode == HttpsURLConnection.HTTP_NOT_FOUND && /* 404 */ + error.code == MatrixError.M_NOT_FOUND -fun Throwable.isLimitExceededError() = - this is Failure.ServerError && - httpCode == 429 && - error.code == MatrixError.M_LIMIT_EXCEEDED +fun Throwable.isTokenError() = this is Failure.ServerError && + (error.code == MatrixError.M_UNKNOWN_TOKEN || + error.code == MatrixError.M_MISSING_TOKEN || + error.code == MatrixError.ORG_MATRIX_EXPIRED_ACCOUNT) -fun Throwable.shouldBeRetried(): Boolean { - return this is Failure.NetworkConnection || - this is IOException || - this.isLimitExceededError() -} +fun Throwable.isLimitExceededError() = this is Failure.ServerError && + httpCode == 429 && + error.code == MatrixError.M_LIMIT_EXCEEDED + +fun Throwable.shouldBeRetried() = this is Failure.NetworkConnection || + this is IOException || + isLimitExceededError() /** * Get the retry delay in case of rate limit exceeded error, adding 100 ms, of defaultValue otherwise @@ -58,17 +58,33 @@ fun Throwable.getRetryDelay(defaultValue: Long): Long { ?: defaultValue } -fun Throwable.isInvalidPassword(): Boolean { - return this is Failure.ServerError && - error.code == MatrixError.M_FORBIDDEN && - error.message == "Invalid password" -} +fun Throwable.isUsernameInUse() = this is Failure.ServerError && + error.code == MatrixError.M_USER_IN_USE -fun Throwable.isInvalidUIAAuth(): Boolean { - return this is Failure.ServerError && - error.code == MatrixError.M_FORBIDDEN && - error.flows != null -} +fun Throwable.isInvalidUsername() = this is Failure.ServerError && + error.code == MatrixError.M_INVALID_USERNAME + +fun Throwable.isInvalidPassword() = this is Failure.ServerError && + error.code == MatrixError.M_FORBIDDEN && + error.message == "Invalid password" + +fun Throwable.isRegistrationDisabled() = this is Failure.ServerError && + error.code == MatrixError.M_FORBIDDEN && + httpCode == HttpsURLConnection.HTTP_FORBIDDEN + +fun Throwable.isWeakPassword() = this is Failure.ServerError && + error.code == MatrixError.M_WEAK_PASSWORD + +fun Throwable.isLoginEmailUnknown() = this is Failure.ServerError && + error.code == MatrixError.M_FORBIDDEN && + error.message.isEmpty() + +fun Throwable.isInvalidUIAAuth() = this is Failure.ServerError && + error.code == MatrixError.M_FORBIDDEN && + error.flows != null + +fun Throwable.isHomeserverUnavailable() = this is Failure.NetworkConnection && + this.ioException is UnknownHostException /** * Try to convert to a RegistrationFlowResponse. Return null in the cases it's not possible @@ -100,13 +116,11 @@ fun Throwable.toRegistrationFlowResponse(): RegistrationFlowResponse? { } } -fun Throwable.isRegistrationAvailabilityError(): Boolean { - return this is Failure.ServerError && - httpCode == HttpsURLConnection.HTTP_BAD_REQUEST && /* 400 */ - (error.code == MatrixError.M_USER_IN_USE || - error.code == MatrixError.M_INVALID_USERNAME || - error.code == MatrixError.M_EXCLUSIVE) -} +fun Throwable.isRegistrationAvailabilityError() = this is Failure.ServerError && + httpCode == HttpsURLConnection.HTTP_BAD_REQUEST && /* 400 */ + (error.code == MatrixError.M_USER_IN_USE || + error.code == MatrixError.M_INVALID_USERNAME || + error.code == MatrixError.M_EXCLUSIVE) /** * Try to convert to a ScanFailure. Return null in the cases it's not possible 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 8f1bbb6941..be139fd82b 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 @@ -17,8 +17,8 @@ package org.matrix.android.sdk.api.failure import org.matrix.android.sdk.api.auth.registration.RegistrationFlowResponse +import org.matrix.android.sdk.api.network.ssl.Fingerprint import org.matrix.android.sdk.api.session.crypto.MXCryptoError -import org.matrix.android.sdk.internal.network.ssl.Fingerprint import java.io.IOException /** diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/failure/GlobalError.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/failure/GlobalError.kt index 50c84da02b..5b4896f95f 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/failure/GlobalError.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/failure/GlobalError.kt @@ -16,12 +16,17 @@ package org.matrix.android.sdk.api.failure -import org.matrix.android.sdk.internal.network.ssl.Fingerprint +import org.matrix.android.sdk.api.network.ssl.Fingerprint // This class will be sent to the bus sealed class GlobalError { data class InvalidToken(val softLogout: Boolean) : GlobalError() data class ConsentNotGivenError(val consentUri: String) : GlobalError() data class CertificateError(val fingerprint: Fingerprint) : GlobalError() + + /** + * The SDK requires the app (which should request the user) to perform an initial sync. + */ + data class InitialSyncRequest(val reason: InitialSyncRequestReason) : GlobalError() object ExpiredAccount : GlobalError() } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/failure/InitialSyncRequestReason.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/failure/InitialSyncRequestReason.kt new file mode 100644 index 0000000000..ebe07823f4 --- /dev/null +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/failure/InitialSyncRequestReason.kt @@ -0,0 +1,27 @@ +/* + * Copyright (c) 2022 The Matrix.org Foundation C.I.C. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.matrix.android.sdk.api.failure + +/** + * This enum provide the reason why the SDK request an initial sync to the application + */ +enum class InitialSyncRequestReason { + /** + * The list of ignored users has changed, and at least one user who was ignored is not ignored anymore + */ + IGNORED_USERS_LIST_CHANGE, +} diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/failure/MatrixError.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/failure/MatrixError.kt index 1bc8636103..32e1aca17d 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/failure/MatrixError.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/failure/MatrixError.kt @@ -18,8 +18,8 @@ package org.matrix.android.sdk.api.failure import com.squareup.moshi.Json import com.squareup.moshi.JsonClass +import org.matrix.android.sdk.api.session.uia.InteractiveAuthenticationFlow import org.matrix.android.sdk.api.util.JsonDict -import org.matrix.android.sdk.internal.auth.data.InteractiveAuthenticationFlow /** * This data class holds the error defined by the matrix specifications. diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/network/ssl/Fingerprint.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/network/ssl/Fingerprint.kt similarity index 95% rename from matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/network/ssl/Fingerprint.kt rename to matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/network/ssl/Fingerprint.kt index b096bd6c87..93e93fd292 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/network/ssl/Fingerprint.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/network/ssl/Fingerprint.kt @@ -14,10 +14,11 @@ * limitations under the License. */ -package org.matrix.android.sdk.internal.network.ssl +package org.matrix.android.sdk.api.network.ssl import com.squareup.moshi.Json import com.squareup.moshi.JsonClass +import org.matrix.android.sdk.internal.network.ssl.CertUtil import java.security.cert.CertificateException import java.security.cert.X509Certificate diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/LiveEventListener.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/LiveEventListener.kt index 6fda65953a..b4b283c86a 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/LiveEventListener.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/LiveEventListener.kt @@ -25,9 +25,9 @@ interface LiveEventListener { fun onPaginatedEvent(roomId: String, event: Event) - fun onEventDecrypted(eventId: String, roomId: String, clearEvent: JsonDict) + fun onEventDecrypted(event: Event, clearEvent: JsonDict) - fun onEventDecryptionError(eventId: String, roomId: String, throwable: Throwable) + fun onEventDecryptionError(event: Event, throwable: Throwable) fun onLiveToDeviceEvent(event: Event) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/Session.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/Session.kt index be924e2063..19502f0b46 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/Session.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/Session.kt @@ -24,10 +24,8 @@ import org.matrix.android.sdk.api.MatrixCoroutineDispatchers import org.matrix.android.sdk.api.auth.data.SessionParams import org.matrix.android.sdk.api.failure.GlobalError import org.matrix.android.sdk.api.federation.FederationService -import org.matrix.android.sdk.api.pushrules.PushRuleService import org.matrix.android.sdk.api.session.account.AccountService import org.matrix.android.sdk.api.session.accountdata.SessionAccountDataService -import org.matrix.android.sdk.api.session.cache.CacheService import org.matrix.android.sdk.api.session.call.CallSignalingService import org.matrix.android.sdk.api.session.content.ContentUploadStateTracker import org.matrix.android.sdk.api.session.content.ContentUrlResolver @@ -47,6 +45,7 @@ import org.matrix.android.sdk.api.session.permalinks.PermalinkService import org.matrix.android.sdk.api.session.presence.PresenceService import org.matrix.android.sdk.api.session.profile.ProfileService import org.matrix.android.sdk.api.session.pushers.PushersService +import org.matrix.android.sdk.api.session.pushrules.PushRuleService import org.matrix.android.sdk.api.session.room.RoomDirectoryService import org.matrix.android.sdk.api.session.room.RoomService import org.matrix.android.sdk.api.session.search.SearchService @@ -68,26 +67,7 @@ import org.matrix.android.sdk.api.session.widgets.WidgetService * This interface defines interactions with a session. * An instance of a session will be provided by the SDK. */ -interface Session : - RoomService, - RoomDirectoryService, - GroupService, - UserService, - CacheService, - SignOutService, - FilterService, - TermsService, - EventService, - ProfileService, - PresenceService, - PushRuleService, - PushersService, - SyncStatusService, - HomeServerCapabilitiesService, - SecureStorageService, - AccountService, - ToDeviceService, - EventStreamService { +interface Session { val coroutineDispatchers: MatrixCoroutineDispatchers @@ -144,6 +124,11 @@ interface Session : */ fun stopSync() + /** + * Clear cache of the session + */ + suspend fun clearCache() + /** * This method allows to listen the sync state. * @return a [LiveData] of [SyncState]. @@ -206,6 +191,96 @@ interface Session : */ fun identityService(): IdentityService + /** + * Returns the HomeServerCapabilities service associated with the session + */ + fun homeServerCapabilitiesService(): HomeServerCapabilitiesService + + /** + * Returns the RoomService associated with the session + */ + fun roomService(): RoomService + + /** + * Returns the RoomDirectoryService associated with the session + */ + fun roomDirectoryService(): RoomDirectoryService + + /** + * Returns the GroupService associated with the session + */ + fun groupService(): GroupService + + /** + * Returns the UserService associated with the session + */ + fun userService(): UserService + + /** + * Returns the SignOutService associated with the session + */ + fun signOutService(): SignOutService + + /** + * Returns the FilterService associated with the session + */ + fun filterService(): FilterService + + /** + * Returns the PushRuleService associated with the session + */ + fun pushRuleService(): PushRuleService + + /** + * Returns the PushersService associated with the session + */ + fun pushersService(): PushersService + + /** + * Returns the EventService associated with the session + */ + fun eventService(): EventService + + /** + * Returns the TermsService associated with the session + */ + fun termsService(): TermsService + + /** + * Returns the SyncStatusService associated with the session + */ + fun syncStatusService(): SyncStatusService + + /** + * Returns the SecureStorageService associated with the session + */ + fun secureStorageService(): SecureStorageService + + /** + * Returns the ProfileService associated with the session + */ + fun profileService(): ProfileService + + /** + * Returns the PresenceService associated with the session + */ + fun presenceService(): PresenceService + + /** + * Returns the AccountService associated with the session + */ + fun accountService(): AccountService + + /** + * Returns the ToDeviceService associated with the session + */ + fun toDeviceService(): ToDeviceService + + /** + * Returns the EventStreamService associated with the session + */ + fun eventStreamService(): EventStreamService + /** * Returns the widget service associated with the session */ @@ -266,6 +341,11 @@ interface Session : */ fun accountDataService(): SessionAccountDataService + /** + * Returns the SharedSecretStorageService associated with the session + */ + fun sharedSecretStorageService(): SharedSecretStorageService + /** * Add a listener to the session. * @param listener the listener to add. @@ -298,12 +378,11 @@ interface Session : * Possible cases: * - The access token is not valid anymore, * - a M_CONSENT_NOT_GIVEN error has been received from the homeserver + * See [GlobalError] for all the possible cases */ fun onGlobalError(session: Session, globalError: GlobalError) = Unit } - val sharedSecretStorageService: SharedSecretStorageService - fun getUiaSsoFallbackUrl(authenticationSessionId: String): String /** diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/SessionExtensions.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/SessionExtensions.kt new file mode 100644 index 0000000000..aeb0e7e4ee --- /dev/null +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/SessionExtensions.kt @@ -0,0 +1,36 @@ +/* + * Copyright (c) 2022 The Matrix.org Foundation C.I.C. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.matrix.android.sdk.api.session + +import org.matrix.android.sdk.api.session.room.Room +import org.matrix.android.sdk.api.session.room.model.RoomSummary +import org.matrix.android.sdk.api.session.user.model.User + +/** + * Get a room using the RoomService of a Session + */ +fun Session.getRoom(roomId: String): Room? = roomService().getRoom(roomId) + +/** + * Get a room summary using the RoomService of a Session + */ +fun Session.getRoomSummary(roomIdOrAlias: String): RoomSummary? = roomService().getRoomSummary(roomIdOrAlias) + +/** + * Get a user using the UserService of a Session + */ +fun Session.getUser(userId: String): User? = userService().getUser(userId) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/ToDeviceService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/ToDeviceService.kt index 45fd39fa95..d7afad5b6c 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/ToDeviceService.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/ToDeviceService.kt @@ -16,8 +16,8 @@ package org.matrix.android.sdk.api.session +import org.matrix.android.sdk.api.session.crypto.model.MXUsersDevicesMap import org.matrix.android.sdk.api.session.events.model.Content -import org.matrix.android.sdk.internal.crypto.model.MXUsersDevicesMap import java.util.UUID interface ToDeviceService { diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/content/ContentUrlResolver.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/content/ContentUrlResolver.kt index 3dd096e144..523d60359b 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/content/ContentUrlResolver.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/content/ContentUrlResolver.kt @@ -16,7 +16,7 @@ package org.matrix.android.sdk.api.session.content -import org.matrix.android.sdk.internal.crypto.attachments.ElementToDecrypt +import org.matrix.android.sdk.api.session.crypto.attachments.ElementToDecrypt /** * This interface defines methods for accessing content from the current session. diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/contentscanner/ContentScannerService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/contentscanner/ContentScannerService.kt index 1dd7bab01c..7a85a89058 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/contentscanner/ContentScannerService.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/contentscanner/ContentScannerService.kt @@ -17,8 +17,8 @@ package org.matrix.android.sdk.api.session.contentscanner import androidx.lifecycle.LiveData +import org.matrix.android.sdk.api.session.crypto.attachments.ElementToDecrypt import org.matrix.android.sdk.api.util.Optional -import org.matrix.android.sdk.internal.crypto.attachments.ElementToDecrypt interface ContentScannerService { 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 65f69e17c9..d6d1248de7 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 @@ -23,25 +23,24 @@ import org.matrix.android.sdk.api.MatrixCallback import org.matrix.android.sdk.api.auth.UserInteractiveAuthInterceptor import org.matrix.android.sdk.api.listeners.ProgressListener import org.matrix.android.sdk.api.session.crypto.crosssigning.CrossSigningService +import org.matrix.android.sdk.api.session.crypto.crosssigning.DeviceTrustLevel import org.matrix.android.sdk.api.session.crypto.keysbackup.KeysBackupService import org.matrix.android.sdk.api.session.crypto.keyshare.GossipingRequestListener +import org.matrix.android.sdk.api.session.crypto.model.CryptoDeviceInfo +import org.matrix.android.sdk.api.session.crypto.model.DeviceInfo +import org.matrix.android.sdk.api.session.crypto.model.DevicesListResponse +import org.matrix.android.sdk.api.session.crypto.model.ImportRoomKeysResult +import org.matrix.android.sdk.api.session.crypto.model.IncomingRoomKeyRequest +import org.matrix.android.sdk.api.session.crypto.model.MXDeviceInfo +import org.matrix.android.sdk.api.session.crypto.model.MXEncryptEventContentResult +import org.matrix.android.sdk.api.session.crypto.model.MXEventDecryptionResult +import org.matrix.android.sdk.api.session.crypto.model.MXUsersDevicesMap +import org.matrix.android.sdk.api.session.crypto.model.OutgoingRoomKeyRequest +import org.matrix.android.sdk.api.session.crypto.model.RoomKeyRequestBody import org.matrix.android.sdk.api.session.crypto.verification.VerificationService import org.matrix.android.sdk.api.session.events.model.Content import org.matrix.android.sdk.api.session.events.model.Event -import org.matrix.android.sdk.internal.crypto.IncomingRoomKeyRequest -import org.matrix.android.sdk.internal.crypto.MXEventDecryptionResult -import org.matrix.android.sdk.internal.crypto.NewSessionListener -import org.matrix.android.sdk.internal.crypto.OutgoingRoomKeyRequest -import org.matrix.android.sdk.internal.crypto.crosssigning.DeviceTrustLevel -import org.matrix.android.sdk.internal.crypto.model.CryptoDeviceInfo -import org.matrix.android.sdk.internal.crypto.model.ImportRoomKeysResult -import org.matrix.android.sdk.internal.crypto.model.MXDeviceInfo -import org.matrix.android.sdk.internal.crypto.model.MXEncryptEventContentResult -import org.matrix.android.sdk.internal.crypto.model.MXUsersDevicesMap -import org.matrix.android.sdk.internal.crypto.model.event.RoomKeyWithHeldContent -import org.matrix.android.sdk.internal.crypto.model.rest.DeviceInfo -import org.matrix.android.sdk.internal.crypto.model.rest.DevicesListResponse -import org.matrix.android.sdk.internal.crypto.model.rest.RoomKeyRequestBody +import org.matrix.android.sdk.api.session.events.model.content.RoomKeyWithHeldContent interface CryptoService { @@ -113,6 +112,7 @@ interface CryptoService { fun isRoomEncrypted(roomId: String): Boolean + // TODO This could be removed from this interface fun encryptEventContent(eventContent: Content, eventType: String, roomId: String, @@ -140,7 +140,6 @@ interface CryptoService { fun getLiveCryptoDeviceInfo(userIds: List): LiveData> fun addNewSessionListener(newSessionListener: NewSessionListener) - fun removeSessionListener(listener: NewSessionListener) fun getOutgoingRoomKeyRequests(): List 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 4956278a10..5ff4b54b11 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 @@ -16,8 +16,8 @@ package org.matrix.android.sdk.api.session.crypto -import org.matrix.android.sdk.internal.crypto.model.CryptoDeviceInfo -import org.matrix.android.sdk.internal.crypto.model.MXUsersDevicesMap +import org.matrix.android.sdk.api.session.crypto.model.CryptoDeviceInfo +import org.matrix.android.sdk.api.session.crypto.model.MXUsersDevicesMap import org.matrix.olm.OlmException /** diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/crypto/NewSessionListener.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/crypto/NewSessionListener.kt new file mode 100644 index 0000000000..73cbf5fb78 --- /dev/null +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/crypto/NewSessionListener.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.api.session.crypto + +/** + * This listener notifies on new Megolm sessions being created + */ +interface NewSessionListener { + + /** + * @param roomId the room id where the new Megolm session has been created for, may be null when importing from external sessions + * @param senderKey the sender key of the device which the Megolm session is shared with + * @param sessionId the session id of the Megolm session + */ + fun onNewSession(roomId: String?, senderKey: String, sessionId: String) +} diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/attachments/ElementToDecrypt.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/crypto/attachments/ElementToDecrypt.kt similarity index 90% rename from matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/attachments/ElementToDecrypt.kt rename to matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/crypto/attachments/ElementToDecrypt.kt index 3d00e178a0..de168ac6e5 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/attachments/ElementToDecrypt.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/crypto/attachments/ElementToDecrypt.kt @@ -14,11 +14,11 @@ * limitations under the License. */ -package org.matrix.android.sdk.internal.crypto.attachments +package org.matrix.android.sdk.api.session.crypto.attachments import android.os.Parcelable import kotlinx.parcelize.Parcelize -import org.matrix.android.sdk.internal.crypto.model.rest.EncryptedFileInfo +import org.matrix.android.sdk.api.session.crypto.model.EncryptedFileInfo fun EncryptedFileInfo.toElementToDecrypt(): ElementToDecrypt? { // Check the validity of some fields 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 359e33cc2c..46b131f613 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 @@ -20,9 +20,6 @@ import androidx.lifecycle.LiveData import org.matrix.android.sdk.api.MatrixCallback import org.matrix.android.sdk.api.auth.UserInteractiveAuthInterceptor import org.matrix.android.sdk.api.util.Optional -import org.matrix.android.sdk.internal.crypto.crosssigning.DeviceTrustResult -import org.matrix.android.sdk.internal.crypto.crosssigning.UserTrustResult -import org.matrix.android.sdk.internal.crypto.store.PrivateKeysInfo interface CrossSigningService { diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/model/CryptoCrossSigningKey.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/crypto/crosssigning/CryptoCrossSigningKey.kt similarity index 95% rename from matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/model/CryptoCrossSigningKey.kt rename to matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/crypto/crosssigning/CryptoCrossSigningKey.kt index 606d2e3fc0..11996e673e 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/model/CryptoCrossSigningKey.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/crypto/crosssigning/CryptoCrossSigningKey.kt @@ -14,9 +14,10 @@ * limitations under the License. */ -package org.matrix.android.sdk.internal.crypto.model +package org.matrix.android.sdk.api.session.crypto.crosssigning -import org.matrix.android.sdk.internal.crypto.crosssigning.DeviceTrustLevel +import org.matrix.android.sdk.internal.crypto.model.CryptoInfo +import org.matrix.android.sdk.internal.crypto.model.CryptoInfoMapper import org.matrix.android.sdk.internal.crypto.model.rest.RestKeyInfo data class CryptoCrossSigningKey( diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/crosssigning/DeviceTrustLevel.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/crypto/crosssigning/DeviceTrustLevel.kt similarity index 87% rename from matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/crosssigning/DeviceTrustLevel.kt rename to matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/crypto/crosssigning/DeviceTrustLevel.kt index fa0098e4a4..a0ab5b3b95 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/crosssigning/DeviceTrustLevel.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/crypto/crosssigning/DeviceTrustLevel.kt @@ -1,5 +1,5 @@ /* - * Copyright 2020 The Matrix.org Foundation C.I.C. + * Copyright (c) 2022 The Matrix.org Foundation C.I.C. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.matrix.android.sdk.internal.crypto.crosssigning +package org.matrix.android.sdk.api.session.crypto.crosssigning data class DeviceTrustLevel( val crossSigningVerified: Boolean, diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/crosssigning/DeviceTrustResult.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/crypto/crosssigning/DeviceTrustResult.kt similarity index 88% rename from matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/crosssigning/DeviceTrustResult.kt rename to matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/crypto/crosssigning/DeviceTrustResult.kt index 6e7c620a03..777d34221f 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/crosssigning/DeviceTrustResult.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/crypto/crosssigning/DeviceTrustResult.kt @@ -1,5 +1,5 @@ /* - * Copyright 2020 The Matrix.org Foundation C.I.C. + * Copyright (c) 2022 The Matrix.org Foundation C.I.C. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -13,9 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.matrix.android.sdk.internal.crypto.crosssigning - -import org.matrix.android.sdk.api.session.crypto.crosssigning.MXCrossSigningInfo +package org.matrix.android.sdk.api.session.crypto.crosssigning sealed class DeviceTrustResult { data class Success(val level: DeviceTrustLevel) : DeviceTrustResult() diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/crypto/crosssigning/MXCrossSigningInfo.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/crypto/crosssigning/MXCrossSigningInfo.kt index 20ee68d228..9604decd62 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/crypto/crosssigning/MXCrossSigningInfo.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/crypto/crosssigning/MXCrossSigningInfo.kt @@ -16,9 +16,6 @@ package org.matrix.android.sdk.api.session.crypto.crosssigning -import org.matrix.android.sdk.internal.crypto.model.CryptoCrossSigningKey -import org.matrix.android.sdk.internal.crypto.model.KeyUsage - data class MXCrossSigningInfo( val userId: String, val crossSigningKeys: List diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/PrivateKeysInfo.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/crypto/crosssigning/PrivateKeysInfo.kt similarity index 92% rename from matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/PrivateKeysInfo.kt rename to matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/crypto/crosssigning/PrivateKeysInfo.kt index 04793f185a..f15d7dc598 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/PrivateKeysInfo.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/crypto/crosssigning/PrivateKeysInfo.kt @@ -14,7 +14,7 @@ * limitations under the License. */ -package org.matrix.android.sdk.internal.crypto.store +package org.matrix.android.sdk.api.session.crypto.crosssigning data class PrivateKeysInfo( val master: String? = null, diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/crosssigning/UserTrustResult.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/crypto/crosssigning/UserTrustResult.kt similarity index 83% rename from matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/crosssigning/UserTrustResult.kt rename to matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/crypto/crosssigning/UserTrustResult.kt index 20e7ca09ab..7fc815cd20 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/crosssigning/UserTrustResult.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/crypto/crosssigning/UserTrustResult.kt @@ -1,5 +1,5 @@ /* - * Copyright 2020 The Matrix.org Foundation C.I.C. + * Copyright (c) 2022 The Matrix.org Foundation C.I.C. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -14,10 +14,7 @@ * limitations under the License. */ -package org.matrix.android.sdk.internal.crypto.crosssigning - -import org.matrix.android.sdk.api.session.crypto.crosssigning.MXCrossSigningInfo -import org.matrix.android.sdk.internal.crypto.model.CryptoCrossSigningKey +package org.matrix.android.sdk.api.session.crypto.crosssigning sealed class UserTrustResult { object Success : UserTrustResult() diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/extensions/Try.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/crypto/keysbackup/KeysBackupLastVersionResult.kt similarity index 50% rename from matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/extensions/Try.kt rename to matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/crypto/keysbackup/KeysBackupLastVersionResult.kt index 2ce0534b49..a7e985cea9 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/extensions/Try.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/crypto/keysbackup/KeysBackupLastVersionResult.kt @@ -1,5 +1,5 @@ /* - * Copyright 2020 The Matrix.org Foundation C.I.C. + * Copyright (c) 2022 The Matrix.org Foundation C.I.C. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -14,27 +14,15 @@ * limitations under the License. */ -package org.matrix.android.sdk.internal.extensions +package org.matrix.android.sdk.api.session.crypto.keysbackup -import arrow.core.Failure -import arrow.core.Success -import arrow.core.Try -import arrow.core.TryOf -import arrow.core.fix - -inline fun TryOf.onError(f: (Throwable) -> Unit): Try = fix() - .fold( - { - f(it) - Failure(it) - }, - { Success(it) } - ) - -/** - * Same as doOnNext for Observables - */ -inline fun Try.alsoDo(f: (A) -> Unit) = map { - f(it) - it +sealed interface KeysBackupLastVersionResult { + // No Keys backup found (404 error) + object NoKeysBackup : KeysBackupLastVersionResult + data class KeysBackup(val keysVersionResult: KeysVersionResult) : KeysBackupLastVersionResult +} + +fun KeysBackupLastVersionResult.toKeysVersionResult(): KeysVersionResult? = when (this) { + is KeysBackupLastVersionResult.KeysBackup -> keysVersionResult + 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 4464427b90..9ff99f8dce 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 @@ -19,21 +19,16 @@ package org.matrix.android.sdk.api.session.crypto.keysbackup import org.matrix.android.sdk.api.MatrixCallback import org.matrix.android.sdk.api.listeners.ProgressListener import org.matrix.android.sdk.api.listeners.StepProgressListener -import org.matrix.android.sdk.internal.crypto.keysbackup.model.KeysBackupVersionTrust -import org.matrix.android.sdk.internal.crypto.keysbackup.model.MegolmBackupCreationInfo -import org.matrix.android.sdk.internal.crypto.keysbackup.model.rest.KeysVersion -import org.matrix.android.sdk.internal.crypto.keysbackup.model.rest.KeysVersionResult -import org.matrix.android.sdk.internal.crypto.model.ImportRoomKeysResult -import org.matrix.android.sdk.internal.crypto.store.SavedKeyBackupKeyInfo +import org.matrix.android.sdk.api.session.crypto.model.ImportRoomKeysResult interface KeysBackupService { /** * Retrieve the current version of the backup from the homeserver * * It can be different than keysBackupVersion. - * @param callback onSuccess(null) will be called if there is no backup on the server + * @param callback Asynchronous callback */ - fun getCurrentVersion(callback: MatrixCallback) + fun getCurrentVersion(callback: MatrixCallback) /** * Create a new keys backup version and enable it, using the information return from [prepareKeysBackupVersion]. @@ -219,4 +214,9 @@ interface KeysBackupService { fun getKeyBackupRecoveryKeyInfo(): SavedKeyBackupKeyInfo? fun isValidRecoveryKeyForCurrentVersion(recoveryKey: String, callback: MatrixCallback) + + fun computePrivateKey(passphrase: String, + privateKeySalt: String, + privateKeyIterations: Int, + progressListener: ProgressListener): ByteArray } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/keysbackup/model/KeysBackupVersionTrust.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/crypto/keysbackup/KeysBackupVersionTrust.kt similarity index 83% rename from matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/keysbackup/model/KeysBackupVersionTrust.kt rename to matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/crypto/keysbackup/KeysBackupVersionTrust.kt index 497cb0eb49..c9a2d4e7a5 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/keysbackup/model/KeysBackupVersionTrust.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/crypto/keysbackup/KeysBackupVersionTrust.kt @@ -14,7 +14,7 @@ * limitations under the License. */ -package org.matrix.android.sdk.internal.crypto.keysbackup.model +package org.matrix.android.sdk.api.session.crypto.keysbackup /** * Data model for response to [KeysBackup.getKeysBackupTrust()]. @@ -24,10 +24,10 @@ data class KeysBackupVersionTrust( * Flag to indicate if the backup is trusted. * true if there is a signature that is valid & from a trusted device. */ - var usable: Boolean = false, + val usable: Boolean, /** * Signatures found in the backup version. */ - var signatures: MutableList = ArrayList() + val signatures: List = emptyList() ) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/keysbackup/model/KeysBackupVersionTrustSignature.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/crypto/keysbackup/KeysBackupVersionTrustSignature.kt similarity index 53% rename from matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/keysbackup/model/KeysBackupVersionTrustSignature.kt rename to matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/crypto/keysbackup/KeysBackupVersionTrustSignature.kt index 1e3db28882..219a328cfd 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/keysbackup/model/KeysBackupVersionTrustSignature.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/crypto/keysbackup/KeysBackupVersionTrustSignature.kt @@ -14,28 +14,27 @@ * limitations under the License. */ -package org.matrix.android.sdk.internal.crypto.keysbackup.model +package org.matrix.android.sdk.api.session.crypto.keysbackup -import org.matrix.android.sdk.internal.crypto.model.CryptoDeviceInfo +import org.matrix.android.sdk.api.session.crypto.model.CryptoDeviceInfo /** * A signature in a `KeysBackupVersionTrust` object. */ -class KeysBackupVersionTrustSignature { +data class KeysBackupVersionTrustSignature( + /** + * The id of the device that signed the backup version. + */ + val deviceId: String?, - /** - * The id of the device that signed the backup version. - */ - var deviceId: String? = null + /** + * The device that signed the backup version. + * Can be null if the device is not known. + */ + val device: CryptoDeviceInfo?, - /** - * The device that signed the backup version. - * Can be null if the device is not known. - */ - var device: CryptoDeviceInfo? = null - - /** - * Flag to indicate the signature from this device is valid. - */ - var valid = false -} + /** + * Flag to indicate the signature from this device is valid. + */ + val valid: Boolean, +) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/keysbackup/model/rest/KeysVersion.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/crypto/keysbackup/KeysVersion.kt similarity index 86% rename from matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/keysbackup/model/rest/KeysVersion.kt rename to matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/crypto/keysbackup/KeysVersion.kt index 7a4c3415fc..1dbfe84dff 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/keysbackup/model/rest/KeysVersion.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/crypto/keysbackup/KeysVersion.kt @@ -1,5 +1,5 @@ /* - * Copyright 2020 The Matrix.org Foundation C.I.C. + * Copyright (c) 2022 The Matrix.org Foundation C.I.C. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -14,7 +14,7 @@ * limitations under the License. */ -package org.matrix.android.sdk.internal.crypto.keysbackup.model.rest +package org.matrix.android.sdk.api.session.crypto.keysbackup import com.squareup.moshi.Json import com.squareup.moshi.JsonClass diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/keysbackup/model/rest/KeysVersionResult.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/crypto/keysbackup/KeysVersionResult.kt similarity index 91% rename from matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/keysbackup/model/rest/KeysVersionResult.kt rename to matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/crypto/keysbackup/KeysVersionResult.kt index 485fd48a8c..f283a34e98 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/keysbackup/model/rest/KeysVersionResult.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/crypto/keysbackup/KeysVersionResult.kt @@ -14,11 +14,12 @@ * limitations under the License. */ -package org.matrix.android.sdk.internal.crypto.keysbackup.model.rest +package org.matrix.android.sdk.api.session.crypto.keysbackup import com.squareup.moshi.Json import com.squareup.moshi.JsonClass import org.matrix.android.sdk.api.util.JsonDict +import org.matrix.android.sdk.internal.crypto.keysbackup.model.rest.KeysAlgorithmAndData @JsonClass(generateAdapter = true) data class KeysVersionResult( diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/keysbackup/model/MegolmBackupAuthData.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/crypto/keysbackup/MegolmBackupAuthData.kt similarity index 79% rename from matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/keysbackup/model/MegolmBackupAuthData.kt rename to matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/crypto/keysbackup/MegolmBackupAuthData.kt index 17c895762c..2a620af843 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/keysbackup/model/MegolmBackupAuthData.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/crypto/keysbackup/MegolmBackupAuthData.kt @@ -14,11 +14,12 @@ * limitations under the License. */ -package org.matrix.android.sdk.internal.crypto.keysbackup.model +package org.matrix.android.sdk.api.session.crypto.keysbackup import com.squareup.moshi.Json import com.squareup.moshi.JsonClass import org.matrix.android.sdk.api.util.JsonDict +import org.matrix.android.sdk.internal.crypto.keysbackup.model.SignalableMegolmBackupAuthData import org.matrix.android.sdk.internal.di.MoshiProvider /** @@ -54,7 +55,7 @@ data class MegolmBackupAuthData( val signatures: Map>? = null ) { - fun toJsonDict(): JsonDict { + internal fun toJsonDict(): JsonDict { val moshi = MoshiProvider.providesMoshi() val adapter = moshi.adapter(Map::class.java) @@ -67,7 +68,7 @@ data class MegolmBackupAuthData( } } - fun signalableJSONDictionary(): JsonDict { + internal fun signalableJSONDictionary(): JsonDict { return SignalableMegolmBackupAuthData( publicKey = publicKey, privateKeySalt = privateKeySalt, @@ -76,20 +77,3 @@ data class MegolmBackupAuthData( .signalableJSONDictionary() } } - -internal data class SignalableMegolmBackupAuthData( - val publicKey: String, - val privateKeySalt: String? = null, - val privateKeyIterations: Int? = null -) { - fun signalableJSONDictionary(): JsonDict = HashMap().apply { - put("public_key", publicKey) - - privateKeySalt?.let { - put("private_key_salt", it) - } - privateKeyIterations?.let { - put("private_key_iterations", it) - } - } -} diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/keysbackup/model/MegolmBackupCreationInfo.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/crypto/keysbackup/MegolmBackupCreationInfo.kt similarity index 94% rename from matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/keysbackup/model/MegolmBackupCreationInfo.kt rename to matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/crypto/keysbackup/MegolmBackupCreationInfo.kt index c668e78a9e..0d708b8d73 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/keysbackup/model/MegolmBackupCreationInfo.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/crypto/keysbackup/MegolmBackupCreationInfo.kt @@ -14,7 +14,7 @@ * limitations under the License. */ -package org.matrix.android.sdk.internal.crypto.keysbackup.model +package org.matrix.android.sdk.api.session.crypto.keysbackup /** * Data retrieved from Olm library. algorithm and authData will be send to the homeserver, and recoveryKey will be displayed to the user diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/keysbackup/util/RecoveryKey.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/crypto/keysbackup/RecoveryKey.kt similarity index 93% rename from matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/keysbackup/util/RecoveryKey.kt rename to matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/crypto/keysbackup/RecoveryKey.kt index 44774fd5a6..85d6ef4365 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/keysbackup/util/RecoveryKey.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/crypto/keysbackup/RecoveryKey.kt @@ -14,8 +14,10 @@ * limitations under the License. */ -package org.matrix.android.sdk.internal.crypto.keysbackup.util +package org.matrix.android.sdk.api.session.crypto.keysbackup +import org.matrix.android.sdk.internal.crypto.keysbackup.util.base58decode +import org.matrix.android.sdk.internal.crypto.keysbackup.util.base58encode import kotlin.experimental.xor /** diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/SavedKeyBackupKeyInfo.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/crypto/keysbackup/SavedKeyBackupKeyInfo.kt similarity index 92% rename from matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/SavedKeyBackupKeyInfo.kt rename to matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/crypto/keysbackup/SavedKeyBackupKeyInfo.kt index a48f4ecef5..7f90fea9af 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/SavedKeyBackupKeyInfo.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/crypto/keysbackup/SavedKeyBackupKeyInfo.kt @@ -14,7 +14,7 @@ * limitations under the License. */ -package org.matrix.android.sdk.internal.crypto.store +package org.matrix.android.sdk.api.session.crypto.keysbackup data class SavedKeyBackupKeyInfo( val recoveryKey: String, diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/crypto/keyshare/GossipingRequestListener.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/crypto/keyshare/GossipingRequestListener.kt index ba2d4ba3f6..3cd36c2ce8 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/crypto/keyshare/GossipingRequestListener.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/crypto/keyshare/GossipingRequestListener.kt @@ -16,9 +16,9 @@ package org.matrix.android.sdk.api.session.crypto.keyshare -import org.matrix.android.sdk.internal.crypto.IncomingRequestCancellation -import org.matrix.android.sdk.internal.crypto.IncomingRoomKeyRequest -import org.matrix.android.sdk.internal.crypto.IncomingSecretShareRequest +import org.matrix.android.sdk.api.session.crypto.model.IncomingRequestCancellation +import org.matrix.android.sdk.api.session.crypto.model.IncomingRoomKeyRequest +import org.matrix.android.sdk.api.session.crypto.model.IncomingSecretShareRequest /** * Room keys events listener diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/crypto/model/CryptoDeviceInfo.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/crypto/model/CryptoDeviceInfo.kt new file mode 100644 index 0000000000..418b1e6ce3 --- /dev/null +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/crypto/model/CryptoDeviceInfo.kt @@ -0,0 +1,75 @@ +/* + * Copyright 2020 The Matrix.org Foundation C.I.C. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.matrix.android.sdk.api.session.crypto.model + +import org.matrix.android.sdk.api.session.crypto.crosssigning.DeviceTrustLevel +import org.matrix.android.sdk.internal.crypto.model.CryptoInfo + +data class CryptoDeviceInfo( + val deviceId: String, + override val userId: String, + val algorithms: List? = null, + override val keys: Map? = null, + override val signatures: Map>? = null, + val unsigned: UnsignedDeviceInfo? = null, + var trustLevel: DeviceTrustLevel? = null, + val isBlocked: Boolean = false, + val firstTimeSeenLocalTs: Long? = null +) : CryptoInfo { + + val isVerified: Boolean + get() = trustLevel?.isVerified() == true + + val isUnknown: Boolean + get() = trustLevel == null + + /** + * @return the fingerprint + */ + fun fingerprint(): String? { + return keys + ?.takeIf { deviceId.isNotBlank() } + ?.get("ed25519:$deviceId") + } + + /** + * @return the identity key + */ + fun identityKey(): String? { + return keys + ?.takeIf { deviceId.isNotBlank() } + ?.get("curve25519:$deviceId") + } + + /** + * @return the display name + */ + fun displayName(): String? { + return unsigned?.deviceDisplayName + } + + override fun signalableJSONDictionary(): Map { + val map = HashMap() + map["device_id"] = deviceId + map["user_id"] = userId + algorithms?.let { map["algorithms"] = it } + keys?.let { map["keys"] = it } + return map + } + + fun shortDebugString() = "$userId|$deviceId" +} diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/model/rest/DeviceInfo.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/crypto/model/DeviceInfo.kt similarity index 96% rename from matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/model/rest/DeviceInfo.kt rename to matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/crypto/model/DeviceInfo.kt index c5cd400342..221d0793d9 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/model/rest/DeviceInfo.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/crypto/model/DeviceInfo.kt @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.matrix.android.sdk.internal.crypto.model.rest +package org.matrix.android.sdk.api.session.crypto.model import com.squareup.moshi.Json import com.squareup.moshi.JsonClass diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/model/rest/DevicesListResponse.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/crypto/model/DevicesListResponse.kt similarity index 94% rename from matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/model/rest/DevicesListResponse.kt rename to matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/crypto/model/DevicesListResponse.kt index eb325f332e..01f3321166 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/model/rest/DevicesListResponse.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/crypto/model/DevicesListResponse.kt @@ -13,7 +13,8 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.matrix.android.sdk.internal.crypto.model.rest + +package org.matrix.android.sdk.api.session.crypto.model import com.squareup.moshi.Json import com.squareup.moshi.JsonClass diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/model/rest/EncryptedFileInfo.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/crypto/model/EncryptedFileInfo.kt similarity index 97% rename from matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/model/rest/EncryptedFileInfo.kt rename to matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/crypto/model/EncryptedFileInfo.kt index 4fc3adb42c..13ad1df476 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/model/rest/EncryptedFileInfo.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/crypto/model/EncryptedFileInfo.kt @@ -13,7 +13,8 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.matrix.android.sdk.internal.crypto.model.rest + +package org.matrix.android.sdk.api.session.crypto.model import com.squareup.moshi.Json import com.squareup.moshi.JsonClass diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/model/rest/EncryptedFileKey.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/crypto/model/EncryptedFileKey.kt similarity index 97% rename from matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/model/rest/EncryptedFileKey.kt rename to matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/crypto/model/EncryptedFileKey.kt index 71f266d7a5..859c6ac43f 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/model/rest/EncryptedFileKey.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/crypto/model/EncryptedFileKey.kt @@ -13,7 +13,8 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.matrix.android.sdk.internal.crypto.model.rest + +package org.matrix.android.sdk.api.session.crypto.model import com.squareup.moshi.Json import com.squareup.moshi.JsonClass diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/model/rest/ForwardedRoomKeyContent.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/crypto/model/ForwardedRoomKeyContent.kt similarity index 95% rename from matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/model/rest/ForwardedRoomKeyContent.kt rename to matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/crypto/model/ForwardedRoomKeyContent.kt index bbc24f0447..3df4ef7c9a 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/model/rest/ForwardedRoomKeyContent.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/crypto/model/ForwardedRoomKeyContent.kt @@ -1,5 +1,5 @@ /* - * Copyright 2020 The Matrix.org Foundation C.I.C. + * Copyright (c) 2022 The Matrix.org Foundation C.I.C. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -13,7 +13,8 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.matrix.android.sdk.internal.crypto.model.rest + +package org.matrix.android.sdk.api.session.crypto.model import com.squareup.moshi.Json import com.squareup.moshi.JsonClass diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/GossipingRequestState.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/crypto/model/GossipingRequestState.kt similarity index 76% rename from matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/GossipingRequestState.kt rename to matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/crypto/model/GossipingRequestState.kt index e398cbfcaf..d9a6f4fcba 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/GossipingRequestState.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/crypto/model/GossipingRequestState.kt @@ -14,12 +14,7 @@ * limitations under the License. */ -package org.matrix.android.sdk.internal.crypto - -enum class GossipRequestType { - KEY, - SECRET -} +package org.matrix.android.sdk.api.session.crypto.model enum class GossipingRequestState { NONE, @@ -34,13 +29,3 @@ enum class GossipingRequestState { CANCELLED_BY_REQUESTER, RE_REQUESTED } - -enum class OutgoingGossipingRequestState { - UNSENT, - SENDING, - SENT, - CANCELLING, - CANCELLED, - FAILED_TO_SEND, - FAILED_TO_CANCEL -} diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/model/rest/GossipingToDeviceObject.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/crypto/model/GossipingToDeviceObject.kt similarity index 70% rename from matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/model/rest/GossipingToDeviceObject.kt rename to matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/crypto/model/GossipingToDeviceObject.kt index e2ae9d1d6c..1922b2bcee 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/model/rest/GossipingToDeviceObject.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/crypto/model/GossipingToDeviceObject.kt @@ -13,10 +13,8 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.matrix.android.sdk.internal.crypto.model.rest -import com.squareup.moshi.Json -import com.squareup.moshi.JsonClass +package org.matrix.android.sdk.api.session.crypto.model /** * Interface representing an room key action request @@ -35,10 +33,3 @@ interface GossipingToDeviceObject : SendToDeviceObject { const val ACTION_SHARE_CANCELLATION = "request_cancellation" } } - -@JsonClass(generateAdapter = true) -data class GossipingDefaultContent( - @Json(name = "action") override val action: String?, - @Json(name = "requesting_device_id") override val requestingDeviceId: String?, - @Json(name = "m.request_id") override val requestId: String? = null -) : GossipingToDeviceObject diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/model/ImportRoomKeysResult.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/crypto/model/ImportRoomKeysResult.kt similarity index 76% rename from matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/model/ImportRoomKeysResult.kt rename to matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/crypto/model/ImportRoomKeysResult.kt index e9d2a1bcd8..b55f0e8747 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/model/ImportRoomKeysResult.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/crypto/model/ImportRoomKeysResult.kt @@ -14,7 +14,9 @@ * limitations under the License. */ -package org.matrix.android.sdk.internal.crypto.model +package org.matrix.android.sdk.api.session.crypto.model -data class ImportRoomKeysResult(val totalNumberOfKeys: Int, - val successfullyNumberOfImportedKeys: Int) +data class ImportRoomKeysResult( + val totalNumberOfKeys: Int, + val successfullyNumberOfImportedKeys: Int +) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/IncomingRequestCancellation.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/crypto/model/IncomingRequestCancellation.kt similarity index 94% rename from matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/IncomingRequestCancellation.kt rename to matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/crypto/model/IncomingRequestCancellation.kt index 181bc0c1b1..74ca7304f7 100755 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/IncomingRequestCancellation.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/crypto/model/IncomingRequestCancellation.kt @@ -14,10 +14,11 @@ * limitations under the License. */ -package org.matrix.android.sdk.internal.crypto +package org.matrix.android.sdk.api.session.crypto.model import org.matrix.android.sdk.api.session.events.model.Event import org.matrix.android.sdk.api.session.events.model.toModel +import org.matrix.android.sdk.internal.crypto.IncomingShareRequestCommon import org.matrix.android.sdk.internal.crypto.model.rest.ShareRequestCancellation /** diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/IncomingRoomKeyRequest.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/crypto/model/IncomingRoomKeyRequest.kt similarity index 92% rename from matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/IncomingRoomKeyRequest.kt rename to matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/crypto/model/IncomingRoomKeyRequest.kt index babc6008a2..45b0926d89 100755 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/IncomingRoomKeyRequest.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/crypto/model/IncomingRoomKeyRequest.kt @@ -14,12 +14,11 @@ * limitations under the License. */ -package org.matrix.android.sdk.internal.crypto +package org.matrix.android.sdk.api.session.crypto.model import org.matrix.android.sdk.api.session.events.model.Event import org.matrix.android.sdk.api.session.events.model.toModel -import org.matrix.android.sdk.internal.crypto.model.rest.RoomKeyRequestBody -import org.matrix.android.sdk.internal.crypto.model.rest.RoomKeyShareRequest +import org.matrix.android.sdk.internal.crypto.IncomingShareRequestCommon /** * IncomingRoomKeyRequest class defines the incoming room keys request. diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/IncomingSecretShareRequest.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/crypto/model/IncomingSecretShareRequest.kt similarity index 94% rename from matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/IncomingSecretShareRequest.kt rename to matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/crypto/model/IncomingSecretShareRequest.kt index d2ee69196c..5afffef1ae 100755 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/IncomingSecretShareRequest.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/crypto/model/IncomingSecretShareRequest.kt @@ -14,11 +14,11 @@ * limitations under the License. */ -package org.matrix.android.sdk.internal.crypto +package org.matrix.android.sdk.api.session.crypto.model import org.matrix.android.sdk.api.session.events.model.Event import org.matrix.android.sdk.api.session.events.model.toModel -import org.matrix.android.sdk.internal.crypto.model.rest.SecretShareRequest +import org.matrix.android.sdk.internal.crypto.IncomingShareRequestCommon /** * IncomingSecretShareRequest class defines the incoming secret keys request. diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/model/MXDeviceInfo.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/crypto/model/MXDeviceInfo.kt similarity index 90% rename from matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/model/MXDeviceInfo.kt rename to matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/crypto/model/MXDeviceInfo.kt index 68cc41005e..286ab2b7d5 100755 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/model/MXDeviceInfo.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/crypto/model/MXDeviceInfo.kt @@ -14,12 +14,11 @@ * limitations under the License. */ -package org.matrix.android.sdk.internal.crypto.model +package org.matrix.android.sdk.api.session.crypto.model import com.squareup.moshi.Json import com.squareup.moshi.JsonClass import org.matrix.android.sdk.api.util.JsonDict -import org.matrix.android.sdk.internal.crypto.model.rest.DeviceKeys import java.io.Serializable @JsonClass(generateAdapter = true) @@ -144,19 +143,6 @@ data class MXDeviceInfo( return map } - /** - * @return a dictionary of the parameters - */ - fun toDeviceKeys(): DeviceKeys { - return DeviceKeys( - userId = userId, - deviceId = deviceId, - algorithms = algorithms!!, - keys = keys!!, - signatures = signatures!! - ) - } - override fun toString(): String { return "MXDeviceInfo $userId:$deviceId" } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/model/MXEncryptEventContentResult.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/crypto/model/MXEncryptEventContentResult.kt similarity index 94% rename from matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/model/MXEncryptEventContentResult.kt rename to matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/crypto/model/MXEncryptEventContentResult.kt index 524bc80843..706e40a769 100755 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/model/MXEncryptEventContentResult.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/crypto/model/MXEncryptEventContentResult.kt @@ -14,7 +14,7 @@ * limitations under the License. */ -package org.matrix.android.sdk.internal.crypto.model +package org.matrix.android.sdk.api.session.crypto.model import org.matrix.android.sdk.api.session.events.model.Content diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/MXEventDecryptionResult.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/crypto/model/MXEventDecryptionResult.kt similarity index 96% rename from matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/MXEventDecryptionResult.kt rename to matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/crypto/model/MXEventDecryptionResult.kt index c66c37574a..0a0ccc2db3 100755 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/MXEventDecryptionResult.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/crypto/model/MXEventDecryptionResult.kt @@ -14,7 +14,7 @@ * limitations under the License. */ -package org.matrix.android.sdk.internal.crypto +package org.matrix.android.sdk.api.session.crypto.model import org.matrix.android.sdk.api.util.JsonDict @@ -22,7 +22,6 @@ import org.matrix.android.sdk.api.util.JsonDict * The result of a (successful) call to decryptEvent. */ data class MXEventDecryptionResult( - /** * The plaintext payload for the event (typically containing "type" and "content" fields). */ diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/crypto/model/MXUsersDevicesMap.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/crypto/model/MXUsersDevicesMap.kt new file mode 100755 index 0000000000..dc5567e908 --- /dev/null +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/crypto/model/MXUsersDevicesMap.kt @@ -0,0 +1,131 @@ +/* + * Copyright 2020 The Matrix.org Foundation C.I.C. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.matrix.android.sdk.api.session.crypto.model + +class MXUsersDevicesMap { + + // A map of maps (userId -> (deviceId -> Object)). + val map = HashMap>() + + /** + * @return the user Ids + */ + val userIds: List + get() = map.keys.toList() + + val isEmpty: Boolean + get() = map.isEmpty() + + /** + * Provides the device ids list for a user id + * FIXME Should maybe return emptyList and not null, to avoid many !! in the code + * + * @param userId the user id + * @return the device ids list + */ + fun getUserDeviceIds(userId: String?): List? { + return if (!userId.isNullOrBlank() && map.containsKey(userId)) { + map[userId]!!.keys.toList() + } else null + } + + /** + * Provides the object for a device id and a user Id + * + * @param deviceId the device id + * @param userId the object id + * @return the object + */ + fun getObject(userId: String?, deviceId: String?): E? { + return if (!userId.isNullOrBlank() && !deviceId.isNullOrBlank()) { + map[userId]?.get(deviceId) + } else null + } + + /** + * Set an object for a dedicated user Id and device Id + * + * @param userId the user Id + * @param deviceId the device id + * @param o the object to set + */ + fun setObject(userId: String?, deviceId: String?, o: E?) { + if (null != o && userId?.isNotBlank() == true && deviceId?.isNotBlank() == true) { + val devices = map.getOrPut(userId) { HashMap() } + devices[deviceId] = o + } + } + + /** + * Defines the objects map for a user Id + * + * @param objectsPerDevices the objects maps + * @param userId the user id + */ + fun setObjects(userId: String?, objectsPerDevices: Map?) { + if (!userId.isNullOrBlank()) { + if (null == objectsPerDevices) { + map.remove(userId) + } else { + map[userId] = HashMap(objectsPerDevices) + } + } + } + + /** + * Removes objects for a dedicated user + * + * @param userId the user id. + */ + fun removeUserObjects(userId: String?) { + if (!userId.isNullOrBlank()) { + map.remove(userId) + } + } + + /** + * Clear the internal dictionary + */ + fun removeAllObjects() { + map.clear() + } + + /** + * Add entries from another MXUsersDevicesMap + * + * @param other the other one + */ + fun addEntriesFromMap(other: MXUsersDevicesMap?) { + if (null != other) { + map.putAll(other.map) + } + } + + override fun toString(): String { + return "MXUsersDevicesMap $map" + } +} + +inline fun MXUsersDevicesMap.forEach(action: (String, String, T) -> Unit) { + userIds.forEach { userId -> + getUserDeviceIds(userId)?.forEach { deviceId -> + getObject(userId, deviceId)?.let { + action(userId, deviceId, it) + } + } + } +} diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/algorithms/olm/OlmDecryptionResult.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/crypto/model/OlmDecryptionResult.kt similarity index 93% rename from matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/algorithms/olm/OlmDecryptionResult.kt rename to matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/crypto/model/OlmDecryptionResult.kt index 955f57afba..9cf2bf75fb 100755 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/algorithms/olm/OlmDecryptionResult.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/crypto/model/OlmDecryptionResult.kt @@ -1,5 +1,5 @@ /* - * Copyright 2020 The Matrix.org Foundation C.I.C. + * Copyright (c) 2022 The Matrix.org Foundation C.I.C. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -14,7 +14,7 @@ * limitations under the License. */ -package org.matrix.android.sdk.internal.crypto.algorithms.olm +package org.matrix.android.sdk.api.session.crypto.model import com.squareup.moshi.Json import com.squareup.moshi.JsonClass diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/util/Exhaustive.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/crypto/model/OutgoingGossipingRequestState.kt similarity index 74% rename from matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/util/Exhaustive.kt rename to matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/crypto/model/OutgoingGossipingRequestState.kt index 097bdaf153..8c1bdf6768 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/util/Exhaustive.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/crypto/model/OutgoingGossipingRequestState.kt @@ -14,7 +14,14 @@ * limitations under the License. */ -package org.matrix.android.sdk.internal.util +package org.matrix.android.sdk.api.session.crypto.model -// Trick to ensure that when block is exhaustive -internal val T.exhaustive: T get() = this +enum class OutgoingGossipingRequestState { + UNSENT, + SENDING, + SENT, + CANCELLING, + CANCELLED, + FAILED_TO_SEND, + FAILED_TO_CANCEL +} diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/OutgoingRoomKeyRequest.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/crypto/model/OutgoingRoomKeyRequest.kt similarity index 72% rename from matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/OutgoingRoomKeyRequest.kt rename to matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/crypto/model/OutgoingRoomKeyRequest.kt index 88025952db..5f35cc908f 100755 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/OutgoingRoomKeyRequest.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/crypto/model/OutgoingRoomKeyRequest.kt @@ -14,10 +14,10 @@ * limitations under the License. */ -package org.matrix.android.sdk.internal.crypto +package org.matrix.android.sdk.api.session.crypto.model import com.squareup.moshi.JsonClass -import org.matrix.android.sdk.internal.crypto.model.rest.RoomKeyRequestBody +import org.matrix.android.sdk.internal.crypto.OutgoingGossipingRequest /** * Represents an outgoing room key request @@ -25,14 +25,14 @@ import org.matrix.android.sdk.internal.crypto.model.rest.RoomKeyRequestBody @JsonClass(generateAdapter = true) data class OutgoingRoomKeyRequest( // RequestBody - var requestBody: RoomKeyRequestBody?, + val requestBody: RoomKeyRequestBody?, // list of recipients for the request - override var recipients: Map>, + override val recipients: Map>, // Unique id for this request. Used for both // an id within the request for later pairing with a cancellation, and for // the transaction id when sending the to_device messages to our local - override var requestId: String, // current state of this request - override var state: OutgoingGossipingRequestState + override val requestId: String, // current state of this request + override val state: OutgoingGossipingRequestState // transaction id for the cancellation, if any // override var cancellationTxnId: String? = null ) : OutgoingGossipingRequest { @@ -43,9 +43,7 @@ data class OutgoingRoomKeyRequest( * @return the room id. */ val roomId: String? - get() = if (null != requestBody) { - requestBody!!.roomId - } else null + get() = requestBody?.roomId /** * Used only for log. @@ -53,7 +51,5 @@ data class OutgoingRoomKeyRequest( * @return the session id */ val sessionId: String? - get() = if (null != requestBody) { - requestBody!!.sessionId - } else null + get() = requestBody?.sessionId } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/crypto/RoomEncryptionTrustLevel.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/crypto/model/RoomEncryptionTrustLevel.kt similarity index 95% rename from matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/crypto/RoomEncryptionTrustLevel.kt rename to matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/crypto/model/RoomEncryptionTrustLevel.kt index 8ba99ad70b..68c7496d58 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/crypto/RoomEncryptionTrustLevel.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/crypto/model/RoomEncryptionTrustLevel.kt @@ -14,7 +14,7 @@ * limitations under the License. */ -package org.matrix.android.sdk.api.crypto +package org.matrix.android.sdk.api.session.crypto.model /** * RoomEncryptionTrustLevel represents the trust level in an encrypted room. diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/model/rest/RoomKeyRequestBody.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/crypto/model/RoomKeyRequestBody.kt similarity index 96% rename from matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/model/rest/RoomKeyRequestBody.kt rename to matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/crypto/model/RoomKeyRequestBody.kt index 3eae2585a5..15163248dc 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/model/rest/RoomKeyRequestBody.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/crypto/model/RoomKeyRequestBody.kt @@ -13,7 +13,8 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.matrix.android.sdk.internal.crypto.model.rest + +package org.matrix.android.sdk.api.session.crypto.model import com.squareup.moshi.Json import com.squareup.moshi.JsonClass diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/model/rest/RoomKeyShareRequest.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/crypto/model/RoomKeyShareRequest.kt similarity index 95% rename from matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/model/rest/RoomKeyShareRequest.kt rename to matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/crypto/model/RoomKeyShareRequest.kt index 68fbf0b805..b6bb4c55af 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/model/rest/RoomKeyShareRequest.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/crypto/model/RoomKeyShareRequest.kt @@ -13,7 +13,8 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.matrix.android.sdk.internal.crypto.model.rest + +package org.matrix.android.sdk.api.session.crypto.model import com.squareup.moshi.Json import com.squareup.moshi.JsonClass diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/model/rest/SecretShareRequest.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/crypto/model/SecretShareRequest.kt similarity index 95% rename from matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/model/rest/SecretShareRequest.kt rename to matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/crypto/model/SecretShareRequest.kt index a4eeb50d8b..6009077806 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/model/rest/SecretShareRequest.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/crypto/model/SecretShareRequest.kt @@ -13,7 +13,8 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.matrix.android.sdk.internal.crypto.model.rest + +package org.matrix.android.sdk.api.session.crypto.model import com.squareup.moshi.Json import com.squareup.moshi.JsonClass diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/model/rest/SendToDeviceObject.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/crypto/model/SendToDeviceObject.kt similarity index 91% rename from matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/model/rest/SendToDeviceObject.kt rename to matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/crypto/model/SendToDeviceObject.kt index b3a76b2a7c..b866cb76cc 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/model/rest/SendToDeviceObject.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/crypto/model/SendToDeviceObject.kt @@ -14,6 +14,6 @@ * limitations under the License. */ -package org.matrix.android.sdk.internal.crypto.model.rest +package org.matrix.android.sdk.api.session.crypto.model interface SendToDeviceObject diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/model/rest/UnsignedDeviceInfo.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/crypto/model/UnsignedDeviceInfo.kt similarity index 94% rename from matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/model/rest/UnsignedDeviceInfo.kt rename to matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/crypto/model/UnsignedDeviceInfo.kt index 5f316486b6..1d9d1fce3e 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/model/rest/UnsignedDeviceInfo.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/crypto/model/UnsignedDeviceInfo.kt @@ -14,7 +14,7 @@ * limitations under the License. */ -package org.matrix.android.sdk.internal.crypto.model.rest +package org.matrix.android.sdk.api.session.crypto.model import com.squareup.moshi.Json import com.squareup.moshi.JsonClass diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/crypto/VerificationState.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/crypto/verification/VerificationState.kt similarity index 87% rename from matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/crypto/VerificationState.kt rename to matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/crypto/verification/VerificationState.kt index 54276a6b51..fe855278a5 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/crypto/VerificationState.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/crypto/verification/VerificationState.kt @@ -1,5 +1,5 @@ /* - * Copyright (c) 2021 The Matrix.org Foundation C.I.C. + * Copyright (c) 2022 The Matrix.org Foundation C.I.C. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -14,7 +14,7 @@ * limitations under the License. */ -package org.matrix.android.sdk.api.crypto +package org.matrix.android.sdk.api.session.crypto.verification enum class VerificationState { REQUEST, 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 f1304f6216..1ce51a2bde 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 @@ -22,6 +22,8 @@ import org.json.JSONObject import org.matrix.android.sdk.api.extensions.tryOrNull import org.matrix.android.sdk.api.failure.MatrixError import org.matrix.android.sdk.api.session.crypto.MXCryptoError +import org.matrix.android.sdk.api.session.crypto.model.OlmDecryptionResult +import org.matrix.android.sdk.api.session.events.model.content.EncryptedEventContent import org.matrix.android.sdk.api.session.room.model.Membership import org.matrix.android.sdk.api.session.room.model.RoomMemberContent import org.matrix.android.sdk.api.session.room.model.message.MessageContent @@ -34,8 +36,7 @@ import org.matrix.android.sdk.api.session.room.send.SendState import org.matrix.android.sdk.api.session.threads.ThreadDetails import org.matrix.android.sdk.api.util.ContentUtils import org.matrix.android.sdk.api.util.JsonDict -import org.matrix.android.sdk.internal.crypto.algorithms.olm.OlmDecryptionResult -import org.matrix.android.sdk.internal.crypto.model.event.EncryptedEventContent +import org.matrix.android.sdk.api.util.MatrixJsonParser import org.matrix.android.sdk.internal.di.MoshiProvider import org.matrix.android.sdk.internal.session.presence.model.PresenceContent import timber.log.Timber @@ -46,7 +47,7 @@ typealias Content = JsonDict * This methods is a facility method to map a json content to a model. */ inline fun Content?.toModel(catchError: Boolean = true): T? { - val moshi = MoshiProvider.providesMoshi() + val moshi = MatrixJsonParser.getMoshi() val moshiAdapter = moshi.adapter(T::class.java) return try { moshiAdapter.fromJsonValue(this) @@ -65,7 +66,7 @@ inline fun Content?.toModel(catchError: Boolean = true): T? { */ @Suppress("UNCHECKED_CAST") inline fun T.toContent(): Content { - val moshi = MoshiProvider.providesMoshi() + val moshi = MatrixJsonParser.getMoshi() val moshiAdapter = moshi.adapter(T::class.java) return moshiAdapter.toJsonValue(this) as Content } @@ -202,7 +203,9 @@ data class Event( fun getDecryptedTextSummary(): String? { if (isRedacted()) return "Message Deleted" val text = getDecryptedValue() ?: run { - if (isPoll()) { return getPollQuestion() ?: "created a poll." } + if (isPoll()) { + return getPollQuestion() ?: "created a poll." + } return null } @@ -300,57 +303,67 @@ data class Event( } } +/** + * Return the value of "content.msgtype", if the Event type is "m.room.message" + * and if the content has it, and if it is a String + */ +fun Event.getMsgType(): String? { + if (getClearType() != EventType.MESSAGE) return null + return getClearContent()?.get(MessageContent.MSG_TYPE_JSON_KEY) as? String +} + fun Event.isTextMessage(): Boolean { - return getClearType() == EventType.MESSAGE && - when (getClearContent()?.get(MessageContent.MSG_TYPE_JSON_KEY)) { - MessageType.MSGTYPE_TEXT, - MessageType.MSGTYPE_EMOTE, - MessageType.MSGTYPE_NOTICE -> true - else -> false - } + return when (getMsgType()) { + MessageType.MSGTYPE_TEXT, + MessageType.MSGTYPE_EMOTE, + MessageType.MSGTYPE_NOTICE -> true + else -> false + } } fun Event.isImageMessage(): Boolean { - return getClearType() == EventType.MESSAGE && - when (getClearContent()?.get(MessageContent.MSG_TYPE_JSON_KEY)) { - MessageType.MSGTYPE_IMAGE -> true - else -> false - } + return when (getMsgType()) { + MessageType.MSGTYPE_IMAGE -> true + else -> false + } } fun Event.isVideoMessage(): Boolean { - return getClearType() == EventType.MESSAGE && - when (getClearContent()?.get(MessageContent.MSG_TYPE_JSON_KEY)) { - MessageType.MSGTYPE_VIDEO -> true - else -> false - } + return when (getMsgType()) { + MessageType.MSGTYPE_VIDEO -> true + else -> false + } } fun Event.isAudioMessage(): Boolean { - return getClearType() == EventType.MESSAGE && - when (getClearContent()?.get(MessageContent.MSG_TYPE_JSON_KEY)) { - MessageType.MSGTYPE_AUDIO -> true - else -> false - } + return when (getMsgType()) { + MessageType.MSGTYPE_AUDIO -> true + else -> false + } } fun Event.isFileMessage(): Boolean { - return getClearType() == EventType.MESSAGE && - when (getClearContent()?.get(MessageContent.MSG_TYPE_JSON_KEY)) { - MessageType.MSGTYPE_FILE -> true - else -> false - } + return when (getMsgType()) { + MessageType.MSGTYPE_FILE -> true + else -> false + } } fun Event.isAttachmentMessage(): Boolean { - return getClearType() == EventType.MESSAGE && - when (getClearContent()?.get(MessageContent.MSG_TYPE_JSON_KEY)) { - MessageType.MSGTYPE_IMAGE, - MessageType.MSGTYPE_AUDIO, - MessageType.MSGTYPE_VIDEO, - MessageType.MSGTYPE_FILE -> true - else -> false - } + return when (getMsgType()) { + MessageType.MSGTYPE_IMAGE, + MessageType.MSGTYPE_AUDIO, + MessageType.MSGTYPE_VIDEO, + MessageType.MSGTYPE_FILE -> true + else -> false + } +} + +fun Event.isLocationMessage(): Boolean { + return when (getMsgType()) { + MessageType.MSGTYPE_LOCATION -> true + else -> false + } } fun Event.isPoll(): Boolean = getClearType() in EventType.POLL_START || getClearType() in EventType.POLL_END @@ -400,7 +413,7 @@ fun Event.isEdition(): Boolean { return getRelationContentForType(RelationType.REPLACE)?.eventId != null } -fun Event.getPresenceContent(): PresenceContent? { +internal fun Event.getPresenceContent(): PresenceContent? { return content.toModel() } @@ -411,4 +424,5 @@ fun Event.getPollContent(): MessagePollContent? { return content.toModel() } -fun Event.supportsNotification() = this.getClearType() in EventType.MESSAGE + EventType.POLL_START +fun Event.supportsNotification() = + this.getClearType() in EventType.MESSAGE + EventType.POLL_START + EventType.STATE_ROOM_BEACON_INFO diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/events/model/EventType.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/events/model/EventType.kt index 22fb9bcbe2..fa3a9f6acd 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/events/model/EventType.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/events/model/EventType.kt @@ -49,6 +49,8 @@ object EventType { const val STATE_ROOM_JOIN_RULES = "m.room.join_rules" const val STATE_ROOM_GUEST_ACCESS = "m.room.guest_access" const val STATE_ROOM_POWER_LEVELS = "m.room.power_levels" + val STATE_ROOM_BEACON_INFO = listOf("org.matrix.msc3672.beacon_info", "m.beacon_info") + val BEACON_LOCATION_DATA = listOf("org.matrix.msc3672.beacon", "m.beacon") const val STATE_SPACE_CHILD = "m.space.child" diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/events/model/UnsignedData.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/events/model/UnsignedData.kt index dfe1db7b1c..630a2fb91a 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/events/model/UnsignedData.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/events/model/UnsignedData.kt @@ -46,3 +46,5 @@ data class UnsignedData( @Json(name = "replaces_state") val replacesState: String? = null ) + +fun UnsignedData?.isRedacted() = this?.redactedEvent != null diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/model/event/EncryptedEventContent.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/events/model/content/EncryptedEventContent.kt similarity index 93% rename from matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/model/event/EncryptedEventContent.kt rename to matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/events/model/content/EncryptedEventContent.kt index 93a6377bbb..4f39bb61e1 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/model/event/EncryptedEventContent.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/events/model/content/EncryptedEventContent.kt @@ -1,5 +1,5 @@ /* - * Copyright 2020 The Matrix.org Foundation C.I.C. + * Copyright (c) 2022 The Matrix.org Foundation C.I.C. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.matrix.android.sdk.internal.crypto.model.event +package org.matrix.android.sdk.api.session.events.model.content import com.squareup.moshi.Json import com.squareup.moshi.JsonClass diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/model/event/EncryptionEventContent.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/events/model/content/EncryptionEventContent.kt similarity index 95% rename from matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/model/event/EncryptionEventContent.kt rename to matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/events/model/content/EncryptionEventContent.kt index dd76ae1d8e..103293ba83 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/model/event/EncryptionEventContent.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/events/model/content/EncryptionEventContent.kt @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.matrix.android.sdk.internal.crypto.model.event +package org.matrix.android.sdk.api.session.events.model.content import com.squareup.moshi.Json import com.squareup.moshi.JsonClass diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/model/event/OlmEventContent.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/events/model/content/OlmEventContent.kt similarity index 94% rename from matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/model/event/OlmEventContent.kt rename to matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/events/model/content/OlmEventContent.kt index 6fd0627022..b972dd20bb 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/model/event/OlmEventContent.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/events/model/content/OlmEventContent.kt @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.matrix.android.sdk.internal.crypto.model.event +package org.matrix.android.sdk.api.session.events.model.content import com.squareup.moshi.Json import com.squareup.moshi.JsonClass diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/model/event/OlmPayloadContent.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/events/model/content/OlmPayloadContent.kt similarity index 96% rename from matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/model/event/OlmPayloadContent.kt rename to matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/events/model/content/OlmPayloadContent.kt index 3ce9d36f90..6060ab5c4b 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/model/event/OlmPayloadContent.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/events/model/content/OlmPayloadContent.kt @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.matrix.android.sdk.internal.crypto.model.event +package org.matrix.android.sdk.api.session.events.model.content import com.squareup.moshi.Json import com.squareup.moshi.JsonClass diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/model/event/RoomKeyContent.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/events/model/content/RoomKeyContent.kt similarity index 90% rename from matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/model/event/RoomKeyContent.kt rename to matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/events/model/content/RoomKeyContent.kt index 7fa0e83725..43a47b818f 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/model/event/RoomKeyContent.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/events/model/content/RoomKeyContent.kt @@ -1,5 +1,5 @@ /* - * Copyright 2020 The Matrix.org Foundation C.I.C. + * Copyright (c) 2022 The Matrix.org Foundation C.I.C. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.matrix.android.sdk.internal.crypto.model.event +package org.matrix.android.sdk.api.session.events.model.content import com.squareup.moshi.Json import com.squareup.moshi.JsonClass diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/model/event/RoomKeyWithHeldContent.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/events/model/content/RoomKeyWithHeldContent.kt similarity index 98% rename from matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/model/event/RoomKeyWithHeldContent.kt rename to matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/events/model/content/RoomKeyWithHeldContent.kt index 4c462357db..a577daf9e4 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/model/event/RoomKeyWithHeldContent.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/events/model/content/RoomKeyWithHeldContent.kt @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.matrix.android.sdk.internal.crypto.model.event +package org.matrix.android.sdk.api.session.events.model.content import com.squareup.moshi.Json import com.squareup.moshi.JsonClass diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/model/event/SecretSendEventContent.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/events/model/content/SecretSendEventContent.kt similarity index 93% rename from matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/model/event/SecretSendEventContent.kt rename to matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/events/model/content/SecretSendEventContent.kt index 4dcca04e94..5099aba403 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/model/event/SecretSendEventContent.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/events/model/content/SecretSendEventContent.kt @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.matrix.android.sdk.internal.crypto.model.event +package org.matrix.android.sdk.api.session.events.model.content import com.squareup.moshi.Json import com.squareup.moshi.JsonClass 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 f76e4be440..e3ccbad249 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/file/FileService.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/file/FileService.kt @@ -17,11 +17,11 @@ package org.matrix.android.sdk.api.session.file import android.net.Uri +import org.matrix.android.sdk.api.session.crypto.attachments.ElementToDecrypt +import org.matrix.android.sdk.api.session.crypto.attachments.toElementToDecrypt import org.matrix.android.sdk.api.session.room.model.message.MessageWithAttachmentContent import org.matrix.android.sdk.api.session.room.model.message.getFileName import org.matrix.android.sdk.api.session.room.model.message.getFileUrl -import org.matrix.android.sdk.internal.crypto.attachments.ElementToDecrypt -import org.matrix.android.sdk.internal.crypto.attachments.toElementToDecrypt import java.io.File /** @@ -45,9 +45,9 @@ interface FileService { * Result will be a decrypted file, stored in the cache folder. url parameter will be used to create unique filename to avoid name collision. */ suspend fun downloadFile(fileName: String, - mimeType: String?, - url: String?, - elementToDecrypt: ElementToDecrypt?): File + mimeType: String?, + url: String?, + elementToDecrypt: ElementToDecrypt?): File suspend fun downloadFile(messageContent: MessageWithAttachmentContent): File = downloadFile( diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/homeserver/HomeServerCapabilities.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/homeserver/HomeServerCapabilities.kt index 9db3876b74..597c1a0ca8 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,7 @@ data class HomeServerCapabilities( /** * True if the home server support threading */ - var canUseThreading: Boolean = false + val canUseThreading: Boolean = false ) { enum class RoomCapabilitySupport { diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/identity/IdentityService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/identity/IdentityService.kt index a22cd572fa..fdcb30a5c8 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/identity/IdentityService.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/identity/IdentityService.kt @@ -16,7 +16,7 @@ package org.matrix.android.sdk.api.session.identity -import org.matrix.android.sdk.internal.session.identity.model.SignInvitationResult +import org.matrix.android.sdk.api.session.identity.model.SignInvitationResult /** * Provides access to the identity server configuration and services identity server can provide diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/identity/model/SignInvitationResult.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/identity/model/SignInvitationResult.kt similarity index 64% rename from matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/identity/model/SignInvitationResult.kt rename to matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/identity/model/SignInvitationResult.kt index 27a3f3209f..b1662b9cf8 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/identity/model/SignInvitationResult.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/identity/model/SignInvitationResult.kt @@ -1,5 +1,5 @@ /* - * Copyright 2021 The Matrix.org Foundation C.I.C. + * Copyright (c) 2022 The Matrix.org Foundation C.I.C. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -14,18 +14,26 @@ * limitations under the License. */ -package org.matrix.android.sdk.internal.session.identity.model +package org.matrix.android.sdk.api.session.identity.model import com.squareup.moshi.JsonClass @JsonClass(generateAdapter = true) data class SignInvitationResult( - /** The Matrix user ID of the user accepting the invitation.*/ + /** + * The Matrix user ID of the user accepting the invitation. + */ val mxid: String, - /** The Matrix user ID of the user who sent the invitation.*/ + /** + * The Matrix user ID of the user who sent the invitation. + */ val sender: String, - /**The token from the call to store- invite..*/ + /** + * The token from the call to store- invite.. + */ val signatures: Map, - /** The token for the invitation */ + /** + * The token for the invitation + */ val token: String ) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/initsync/SyncStatusService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/initsync/SyncStatusService.kt index daab6d9761..759813939f 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/initsync/SyncStatusService.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/initsync/SyncStatusService.kt @@ -28,7 +28,7 @@ interface SyncStatusService { abstract class InitialSyncStatus : Status() object Idle : InitialSyncStatus() - data class Progressing( + data class InitialSyncProgressing( val initSyncStep: InitSyncStep, val percentProgress: Int = 0 ) : InitialSyncStatus() @@ -43,6 +43,7 @@ interface SyncStatusService { val rooms: Int, val toDevice: Int ) : IncrementalSyncStatus() + object IncrementalSyncError : IncrementalSyncStatus() object IncrementalSyncDone : IncrementalSyncStatus() } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/permalinks/MatrixLinkify.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/permalinks/MatrixLinkify.kt index 3e27da0c41..c5d919407a 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/permalinks/MatrixLinkify.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/permalinks/MatrixLinkify.kt @@ -55,7 +55,7 @@ object MatrixLinkify { MatrixPatterns.isRoomId(url) || MatrixPatterns.isGroupId(url) || MatrixPatterns.isEventId(url)) { - url = PermalinkService.MATRIX_TO_URL_BASE + url + url = PermalinkService.MATRIX_TO_URL_BASE + url } val span = MatrixPermalinkSpan(url, callback) spannable.setSpan(span, startPos, endPos, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/permalinks/PermalinkData.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/permalinks/PermalinkData.kt index 85291cf0f6..57aacc98b8 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/permalinks/PermalinkData.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/permalinks/PermalinkData.kt @@ -35,21 +35,21 @@ sealed class PermalinkData { /** * &room_name=Team2 - &room_avatar_url=mxc: - &inviter_name=bob + &room_avatar_url=mxc: + &inviter_name=bob */ @Parcelize data class RoomEmailInviteLink( - val roomId: String, - val email: String, - val signUrl: String, - val roomName: String?, - val roomAvatarUrl: String?, - val inviterName: String?, - val identityServer: String, - val token: String, - val privateKey: String, - val roomType: String? + val roomId: String, + val email: String, + val signUrl: String, + val roomName: String?, + val roomAvatarUrl: String?, + val inviterName: String?, + val identityServer: String, + val token: String, + val privateKey: String, + val roomType: String? ) : PermalinkData(), Parcelable data class UserLink(val userId: String) : PermalinkData() diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/profile/ProfileService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/profile/ProfileService.kt index 05fa24946a..d2c677bb31 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 @@ -21,6 +21,7 @@ import android.net.Uri import androidx.lifecycle.LiveData import org.matrix.android.sdk.api.auth.UserInteractiveAuthInterceptor import org.matrix.android.sdk.api.session.identity.ThreePid +import org.matrix.android.sdk.api.session.user.model.User import org.matrix.android.sdk.api.util.JsonDict import org.matrix.android.sdk.api.util.Optional @@ -118,4 +119,17 @@ interface ProfileService { * Remove a 3Pid from the Matrix account. */ suspend fun deleteThreePid(threePid: ThreePid) + + /** + * Return a User object from a userId + */ + suspend fun getProfileAsUser(userId: String): User { + return getProfile(userId).let { dict -> + User( + userId = userId, + displayName = dict[DISPLAY_NAME_KEY] as? String, + avatarUrl = dict[AVATAR_URL_KEY] as? String + ) + } + } } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/pushrules/Action.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/pushrules/Action.kt similarity index 97% rename from matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/pushrules/Action.kt rename to matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/pushrules/Action.kt index 30289531e7..7790942d84 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/pushrules/Action.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/pushrules/Action.kt @@ -13,9 +13,9 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.matrix.android.sdk.api.pushrules +package org.matrix.android.sdk.api.session.pushrules -import org.matrix.android.sdk.api.pushrules.rest.PushRule +import org.matrix.android.sdk.api.session.pushrules.rest.PushRule import timber.log.Timber sealed class Action { diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/pushrules/Condition.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/pushrules/Condition.kt similarity index 93% rename from matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/pushrules/Condition.kt rename to matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/pushrules/Condition.kt index 04cccf7319..df5b056c2e 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/pushrules/Condition.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/pushrules/Condition.kt @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.matrix.android.sdk.api.pushrules +package org.matrix.android.sdk.api.session.pushrules import org.matrix.android.sdk.api.session.events.model.Event diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/pushrules/ConditionResolver.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/pushrules/ConditionResolver.kt similarity index 96% rename from matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/pushrules/ConditionResolver.kt rename to matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/pushrules/ConditionResolver.kt index 0a7366e5d2..f8a930f987 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/pushrules/ConditionResolver.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/pushrules/ConditionResolver.kt @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.matrix.android.sdk.api.pushrules +package org.matrix.android.sdk.api.session.pushrules import org.matrix.android.sdk.api.session.events.model.Event diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/pushrules/ContainsDisplayNameCondition.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/pushrules/ContainsDisplayNameCondition.kt similarity index 97% rename from matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/pushrules/ContainsDisplayNameCondition.kt rename to matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/pushrules/ContainsDisplayNameCondition.kt index 7f43023873..69dd14ddc2 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/pushrules/ContainsDisplayNameCondition.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/pushrules/ContainsDisplayNameCondition.kt @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.matrix.android.sdk.api.pushrules +package org.matrix.android.sdk.api.session.pushrules import org.matrix.android.sdk.api.session.events.model.Event import org.matrix.android.sdk.api.session.events.model.EventType diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/pushrules/EventMatchCondition.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/pushrules/EventMatchCondition.kt similarity index 98% rename from matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/pushrules/EventMatchCondition.kt rename to matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/pushrules/EventMatchCondition.kt index 65a13b4fec..8875807b8a 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/pushrules/EventMatchCondition.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/pushrules/EventMatchCondition.kt @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.matrix.android.sdk.api.pushrules +package org.matrix.android.sdk.api.session.pushrules import org.matrix.android.sdk.api.session.events.model.Event import org.matrix.android.sdk.internal.di.MoshiProvider diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/pushrules/Kind.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/pushrules/Kind.kt similarity index 96% rename from matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/pushrules/Kind.kt rename to matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/pushrules/Kind.kt index 293a06af9f..463f3c2a73 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/pushrules/Kind.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/pushrules/Kind.kt @@ -14,7 +14,7 @@ * limitations under the License. */ -package org.matrix.android.sdk.api.pushrules +package org.matrix.android.sdk.api.session.pushrules enum class Kind(val value: String) { EventMatch("event_match"), diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/pushrules/PushEvents.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/pushrules/PushEvents.kt similarity index 88% rename from matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/pushrules/PushEvents.kt rename to matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/pushrules/PushEvents.kt index 466e345cad..ee460d7076 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/pushrules/PushEvents.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/pushrules/PushEvents.kt @@ -13,10 +13,10 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.matrix.android.sdk.api.pushrules +package org.matrix.android.sdk.api.session.pushrules -import org.matrix.android.sdk.api.pushrules.rest.PushRule import org.matrix.android.sdk.api.session.events.model.Event +import org.matrix.android.sdk.api.session.pushrules.rest.PushRule data class PushEvents( val matchedEvents: List>, diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/pushrules/PushRuleService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/pushrules/PushRuleService.kt similarity index 91% rename from matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/pushrules/PushRuleService.kt rename to matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/pushrules/PushRuleService.kt index 76885d8545..abbdbf8104 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/pushrules/PushRuleService.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/pushrules/PushRuleService.kt @@ -13,12 +13,12 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.matrix.android.sdk.api.pushrules +package org.matrix.android.sdk.api.session.pushrules import androidx.lifecycle.LiveData -import org.matrix.android.sdk.api.pushrules.rest.PushRule -import org.matrix.android.sdk.api.pushrules.rest.RuleSet import org.matrix.android.sdk.api.session.events.model.Event +import org.matrix.android.sdk.api.session.pushrules.rest.PushRule +import org.matrix.android.sdk.api.session.pushrules.rest.RuleSet interface PushRuleService { /** diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/pushrules/RoomMemberCountCondition.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/pushrules/RoomMemberCountCondition.kt similarity index 97% rename from matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/pushrules/RoomMemberCountCondition.kt rename to matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/pushrules/RoomMemberCountCondition.kt index 328e6dae11..84b2f520ea 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/pushrules/RoomMemberCountCondition.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/pushrules/RoomMemberCountCondition.kt @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.matrix.android.sdk.api.pushrules +package org.matrix.android.sdk.api.session.pushrules import org.matrix.android.sdk.api.session.events.model.Event import org.matrix.android.sdk.internal.session.room.RoomGetter diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/pushrules/RuleIds.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/pushrules/RuleIds.kt similarity index 97% rename from matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/pushrules/RuleIds.kt rename to matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/pushrules/RuleIds.kt index 5b14e97d5e..4f35fb79c3 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/pushrules/RuleIds.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/pushrules/RuleIds.kt @@ -14,7 +14,7 @@ * limitations under the License. */ -package org.matrix.android.sdk.api.pushrules +package org.matrix.android.sdk.api.session.pushrules /** * Known rule ids diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/pushrules/RuleScope.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/pushrules/RuleScope.kt similarity index 92% rename from matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/pushrules/RuleScope.kt rename to matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/pushrules/RuleScope.kt index 7c1edc1aca..307b9db042 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/pushrules/RuleScope.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/pushrules/RuleScope.kt @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.matrix.android.sdk.api.pushrules +package org.matrix.android.sdk.api.session.pushrules object RuleScope { const val GLOBAL = "global" diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/pushrules/RuleSetKey.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/pushrules/RuleSetKey.kt similarity index 95% rename from matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/pushrules/RuleSetKey.kt rename to matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/pushrules/RuleSetKey.kt index 5b6f6713f8..7b8f4c9f95 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/pushrules/RuleSetKey.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/pushrules/RuleSetKey.kt @@ -14,7 +14,7 @@ * limitations under the License. */ -package org.matrix.android.sdk.api.pushrules +package org.matrix.android.sdk.api.session.pushrules /** * Ref: https://matrix.org/docs/spec/client_server/latest#get-matrix-client-r0-pushrules diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/pushrules/SenderNotificationPermissionCondition.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/pushrules/SenderNotificationPermissionCondition.kt similarity index 97% rename from matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/pushrules/SenderNotificationPermissionCondition.kt rename to matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/pushrules/SenderNotificationPermissionCondition.kt index 6675fb0ff5..82f5023c2f 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/pushrules/SenderNotificationPermissionCondition.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/pushrules/SenderNotificationPermissionCondition.kt @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.matrix.android.sdk.api.pushrules +package org.matrix.android.sdk.api.session.pushrules import org.matrix.android.sdk.api.session.events.model.Event import org.matrix.android.sdk.api.session.room.model.PowerLevelsContent diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/pushrules/rest/PushCondition.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/pushrules/rest/PushCondition.kt similarity index 84% rename from matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/pushrules/rest/PushCondition.kt rename to matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/pushrules/rest/PushCondition.kt index b31a1e6343..1fc8329535 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/pushrules/rest/PushCondition.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/pushrules/rest/PushCondition.kt @@ -13,17 +13,17 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.matrix.android.sdk.api.pushrules.rest +package org.matrix.android.sdk.api.session.pushrules.rest import com.squareup.moshi.Json import com.squareup.moshi.JsonClass -import org.matrix.android.sdk.api.pushrules.Condition -import org.matrix.android.sdk.api.pushrules.ContainsDisplayNameCondition -import org.matrix.android.sdk.api.pushrules.EventMatchCondition -import org.matrix.android.sdk.api.pushrules.Kind -import org.matrix.android.sdk.api.pushrules.RoomMemberCountCondition -import org.matrix.android.sdk.api.pushrules.RuleIds -import org.matrix.android.sdk.api.pushrules.SenderNotificationPermissionCondition +import org.matrix.android.sdk.api.session.pushrules.Condition +import org.matrix.android.sdk.api.session.pushrules.ContainsDisplayNameCondition +import org.matrix.android.sdk.api.session.pushrules.EventMatchCondition +import org.matrix.android.sdk.api.session.pushrules.Kind +import org.matrix.android.sdk.api.session.pushrules.RoomMemberCountCondition +import org.matrix.android.sdk.api.session.pushrules.RuleIds +import org.matrix.android.sdk.api.session.pushrules.SenderNotificationPermissionCondition import timber.log.Timber /** diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/pushrules/rest/PushRule.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/pushrules/rest/PushRule.kt similarity index 94% rename from matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/pushrules/rest/PushRule.kt rename to matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/pushrules/rest/PushRule.kt index 31d7770a9f..270ffb2940 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/pushrules/rest/PushRule.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/pushrules/rest/PushRule.kt @@ -14,14 +14,14 @@ * limitations under the License. */ -package org.matrix.android.sdk.api.pushrules.rest +package org.matrix.android.sdk.api.session.pushrules.rest import com.squareup.moshi.Json import com.squareup.moshi.JsonClass import org.matrix.android.sdk.api.extensions.orFalse -import org.matrix.android.sdk.api.pushrules.Action -import org.matrix.android.sdk.api.pushrules.getActions -import org.matrix.android.sdk.api.pushrules.toJson +import org.matrix.android.sdk.api.session.pushrules.Action +import org.matrix.android.sdk.api.session.pushrules.getActions +import org.matrix.android.sdk.api.session.pushrules.toJson /** * Ref: https://matrix.org/docs/spec/client_server/latest#get-matrix-client-r0-pushrules diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/pushrules/rest/RuleSet.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/pushrules/rest/RuleSet.kt similarity index 93% rename from matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/pushrules/rest/RuleSet.kt rename to matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/pushrules/rest/RuleSet.kt index 46f5148714..5bf42b8252 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/pushrules/rest/RuleSet.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/pushrules/rest/RuleSet.kt @@ -13,12 +13,12 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.matrix.android.sdk.api.pushrules.rest +package org.matrix.android.sdk.api.session.pushrules.rest import com.squareup.moshi.Json import com.squareup.moshi.JsonClass -import org.matrix.android.sdk.api.pushrules.RuleIds -import org.matrix.android.sdk.api.pushrules.RuleSetKey +import org.matrix.android.sdk.api.session.pushrules.RuleIds +import org.matrix.android.sdk.api.session.pushrules.RuleSetKey /** * Ref: https://matrix.org/docs/spec/client_server/latest#get-matrix-client-r0-pushrules diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/Room.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/Room.kt index be65b883b3..cc05253be8 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/Room.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/Room.kt @@ -38,7 +38,6 @@ import org.matrix.android.sdk.api.session.room.timeline.TimelineService import org.matrix.android.sdk.api.session.room.typing.TypingService import org.matrix.android.sdk.api.session.room.uploads.UploadsService import org.matrix.android.sdk.api.session.room.version.RoomVersionService -import org.matrix.android.sdk.api.session.search.SearchResult import org.matrix.android.sdk.api.session.space.Space import org.matrix.android.sdk.api.util.Optional @@ -84,26 +83,6 @@ interface Room : */ fun roomSummary(): RoomSummary? - /** - * Generic function to search a term in a room. - * Ref: https://matrix.org/docs/spec/client_server/latest#module-search - * @param searchTerm the term to search - * @param nextBatch the token that retrieved from the previous response. Should be provided to get the next batch of results - * @param orderByRecent if true, the most recent message events will return in the first places of the list - * @param limit the maximum number of events to return. - * @param beforeLimit how many events before the result are returned. - * @param afterLimit how many events after the result are returned. - * @param includeProfile requests that the server returns the historic profile information for the users that sent the events that were returned. - * @return The search result - */ - suspend fun search(searchTerm: String, - nextBatch: String?, - orderByRecent: Boolean, - limit: Int, - beforeLimit: Int, - afterLimit: Int, - includeProfile: Boolean): SearchResult - /** * Use this room as a Space, if the type is correct. */ 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 f506b147df..700e292b0c 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 @@ -18,8 +18,9 @@ package org.matrix.android.sdk.api.session.room import androidx.lifecycle.LiveData import androidx.paging.PagedList -import kotlinx.coroutines.flow.Flow 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.alias.RoomAliasDescription import org.matrix.android.sdk.api.session.room.members.ChangeMembershipState import org.matrix.android.sdk.api.session.room.model.Membership import org.matrix.android.sdk.api.session.room.model.RoomMemberSummary @@ -28,8 +29,6 @@ import org.matrix.android.sdk.api.session.room.model.create.CreateRoomParams import org.matrix.android.sdk.api.session.room.peeking.PeekResult import org.matrix.android.sdk.api.session.room.summary.RoomAggregateNotificationCount import org.matrix.android.sdk.api.util.Optional -import org.matrix.android.sdk.internal.session.identity.model.SignInvitationResult -import org.matrix.android.sdk.internal.session.room.alias.RoomAliasDescription /** * This interface defines methods to get rooms. It's implemented at the session level. @@ -218,9 +217,10 @@ interface RoomService { sortOrder: RoomSortOrder = RoomSortOrder.ACTIVITY): UpdatableLivePageResult /** - * Retrieve a flow on the number of rooms. + * Return a LiveData on the number of rooms + * @param queryParams parameters to query the room summaries. It can be use to keep only joined rooms, for instance. */ - fun getRoomCountFlow(queryParams: RoomSummaryQueryParams): Flow + fun getRoomCountLive(queryParams: RoomSummaryQueryParams): LiveData /** * TODO Doc @@ -242,4 +242,12 @@ interface RoomService { */ fun getFlattenRoomSummaryChildrenOfLive(spaceId: String?, memberships: List = Membership.activeMemberships()): LiveData> + + /** + * Refreshes the RoomSummary LatestPreviewContent for the given @param roomId + * If the roomId is null, all rooms are updated + * + * This is useful for refreshing summary content with encrypted messages after receiving new room keys + */ + fun refreshJoinedRoomSummaryPreviews(roomId: String?) } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/alias/RoomAliasDescription.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/alias/RoomAliasDescription.kt similarity index 94% rename from matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/alias/RoomAliasDescription.kt rename to matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/alias/RoomAliasDescription.kt index d1f93c50be..ce7b03d35b 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/alias/RoomAliasDescription.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/alias/RoomAliasDescription.kt @@ -14,7 +14,7 @@ * limitations under the License. */ -package org.matrix.android.sdk.internal.session.room.alias +package org.matrix.android.sdk.api.session.room.alias import com.squareup.moshi.Json import com.squareup.moshi.JsonClass diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/crypto/RoomCryptoService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/crypto/RoomCryptoService.kt index 445d16b72b..6967e0c455 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/crypto/RoomCryptoService.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/crypto/RoomCryptoService.kt @@ -16,7 +16,7 @@ package org.matrix.android.sdk.api.session.room.crypto -import org.matrix.android.sdk.internal.crypto.MXCRYPTO_ALGORITHM_MEGOLM +import org.matrix.android.sdk.api.crypto.MXCRYPTO_ALGORITHM_MEGOLM interface RoomCryptoService { diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/EventAnnotationsSummary.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/EventAnnotationsSummary.kt index 3a4912e457..0238eb6c8d 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/EventAnnotationsSummary.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/EventAnnotationsSummary.kt @@ -16,9 +16,9 @@ package org.matrix.android.sdk.api.session.room.model data class EventAnnotationsSummary( - var eventId: String, - var reactionsSummary: List = emptyList(), - var editSummary: EditAggregatedSummary? = null, - var pollResponseSummary: PollResponseAggregatedSummary? = null, - var referencesAggregatedSummary: ReferencesAggregatedSummary? = null + val eventId: String, + val reactionsSummary: List = emptyList(), + val editSummary: EditAggregatedSummary? = null, + val pollResponseSummary: PollResponseAggregatedSummary? = null, + val referencesAggregatedSummary: ReferencesAggregatedSummary? = null ) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/PollResponseAggregatedSummary.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/PollResponseAggregatedSummary.kt index a15d8be084..b16852e47d 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/PollResponseAggregatedSummary.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/PollResponseAggregatedSummary.kt @@ -16,13 +16,11 @@ package org.matrix.android.sdk.api.session.room.model data class PollResponseAggregatedSummary( - - var aggregatedContent: PollSummaryContent? = null, - + val aggregatedContent: PollSummaryContent? = null, // If set the poll is closed (Clients SHOULD NOT consider responses after the close event) - var closedTime: Long? = null, + val closedTime: Long? = null, // Clients SHOULD validate that the option in the relationship is a valid option, and ignore the response if invalid - var nbOptions: Int = 0, + val nbOptions: Int = 0, // The list of the eventIDs used to build the summary (might be out of sync if chunked received from message chunk) val sourceEvents: List, val localEchos: List diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/PollSummaryContent.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/PollSummaryContent.kt index f1e4354314..09458ff12e 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/PollSummaryContent.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/PollSummaryContent.kt @@ -24,13 +24,13 @@ import com.squareup.moshi.JsonClass */ @JsonClass(generateAdapter = true) data class PollSummaryContent( - var myVote: String? = null, - // Array of VoteInfo, list is constructed so that there is only one vote by user + val myVote: String? = null, + // List of VoteInfo, list is constructed so that there is only one vote by user // And that optionIndex is valid - var votes: List? = null, - var votesSummary: Map? = null, - var totalVotes: Int = 0, - var winnerVoteCount: Int = 0 + val votes: List? = null, + val votesSummary: Map? = null, + val totalVotes: Int = 0, + val winnerVoteCount: Int = 0 ) @JsonClass(generateAdapter = true) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/ReferencesAggregatedContent.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/ReferencesAggregatedContent.kt index 664d042e18..c6b94c5dd1 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/ReferencesAggregatedContent.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/ReferencesAggregatedContent.kt @@ -17,7 +17,7 @@ package org.matrix.android.sdk.api.session.room.model import com.squareup.moshi.Json import com.squareup.moshi.JsonClass -import org.matrix.android.sdk.api.crypto.VerificationState +import org.matrix.android.sdk.api.session.crypto.verification.VerificationState /** * Contains an aggregated summary info of the references. diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/RoomEncryptionAlgorithm.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/RoomEncryptionAlgorithm.kt index f681216929..5f728a4ed4 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/RoomEncryptionAlgorithm.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/RoomEncryptionAlgorithm.kt @@ -16,7 +16,7 @@ package org.matrix.android.sdk.api.session.room.model -import org.matrix.android.sdk.internal.crypto.MXCRYPTO_ALGORITHM_MEGOLM +import org.matrix.android.sdk.api.crypto.MXCRYPTO_ALGORITHM_MEGOLM sealed class RoomEncryptionAlgorithm { diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/RoomJoinRulesContent.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/RoomJoinRulesContent.kt index 871b299f93..5237b10d52 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/RoomJoinRulesContent.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/RoomJoinRulesContent.kt @@ -36,10 +36,10 @@ data class RoomJoinRulesContent( @Json(name = "allow") val allowList: List? = null ) { val joinRules: RoomJoinRules? = when (_joinRules) { - "public" -> RoomJoinRules.PUBLIC - "invite" -> RoomJoinRules.INVITE - "knock" -> RoomJoinRules.KNOCK - "private" -> RoomJoinRules.PRIVATE + "public" -> RoomJoinRules.PUBLIC + "invite" -> RoomJoinRules.INVITE + "knock" -> RoomJoinRules.KNOCK + "private" -> RoomJoinRules.PRIVATE "restricted" -> RoomJoinRules.RESTRICTED else -> { Timber.w("Invalid value for RoomJoinRules: `$_joinRules`") diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/RoomSummary.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/RoomSummary.kt index c793a04f9d..71c1d8303e 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 @@ -16,7 +16,7 @@ package org.matrix.android.sdk.api.session.room.model -import org.matrix.android.sdk.api.crypto.RoomEncryptionTrustLevel +import org.matrix.android.sdk.api.session.crypto.model.RoomEncryptionTrustLevel import org.matrix.android.sdk.api.session.presence.model.UserPresence import org.matrix.android.sdk.api.session.room.model.tag.RoomTag import org.matrix.android.sdk.api.session.room.send.UserDraft diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/call/CallAnswerContent.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/call/CallAnswerContent.kt index 6b4d782832..67ef85787e 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/call/CallAnswerContent.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/call/CallAnswerContent.kt @@ -44,7 +44,7 @@ data class CallAnswerContent( * Capability advertisement. */ @Json(name = "capabilities") val capabilities: CallCapabilities? = null -) : CallSignalingContent { +) : CallSignalingContent { @JsonClass(generateAdapter = true) data class Answer( diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/call/CallInviteContent.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/call/CallInviteContent.kt index d70e63d122..24c8152f3c 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/call/CallInviteContent.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/call/CallInviteContent.kt @@ -55,7 +55,7 @@ data class CallInviteContent( */ @Json(name = "capabilities") val capabilities: CallCapabilities? = null -) : CallSignalingContent { +) : CallSignalingContent { @JsonClass(generateAdapter = true) data class Offer( /** diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/call/CallNegotiateContent.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/call/CallNegotiateContent.kt index bbbfbe68ab..5c6c6cda01 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/call/CallNegotiateContent.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/call/CallNegotiateContent.kt @@ -47,7 +47,7 @@ data class CallNegotiateContent( */ @Json(name = "version") override val version: String? - ) : CallSignalingContent { +) : CallSignalingContent { @JsonClass(generateAdapter = true) data class Description( /** diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/call/CallReplacesContent.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/call/CallReplacesContent.kt index 7947b7d0bd..e480e013ea 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/call/CallReplacesContent.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/call/CallReplacesContent.kt @@ -61,7 +61,7 @@ data class CallReplacesContent( * Required. The version of the VoIP specification this messages adheres to. */ @Json(name = "version") override val version: String? -) : CallSignalingContent { +) : CallSignalingContent { @JsonClass(generateAdapter = true) data class TargetUser( diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/create/CreateRoomParams.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/create/CreateRoomParams.kt index 5667906000..ce1e0e0d14 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/create/CreateRoomParams.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/create/CreateRoomParams.kt @@ -17,12 +17,12 @@ package org.matrix.android.sdk.api.session.room.model.create import android.net.Uri +import org.matrix.android.sdk.api.crypto.MXCRYPTO_ALGORITHM_MEGOLM import org.matrix.android.sdk.api.session.identity.ThreePid import org.matrix.android.sdk.api.session.room.model.GuestAccess import org.matrix.android.sdk.api.session.room.model.PowerLevelsContent import org.matrix.android.sdk.api.session.room.model.RoomDirectoryVisibility import org.matrix.android.sdk.api.session.room.model.RoomHistoryVisibility -import org.matrix.android.sdk.internal.crypto.MXCRYPTO_ALGORITHM_MEGOLM open class CreateRoomParams { /** diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/livelocation/LiveLocationBeaconContent.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/livelocation/LiveLocationBeaconContent.kt new file mode 100644 index 0000000000..a7c78f6e80 --- /dev/null +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/livelocation/LiveLocationBeaconContent.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.api.session.room.model.livelocation + +import com.squareup.moshi.Json +import com.squareup.moshi.JsonClass +import org.matrix.android.sdk.api.session.events.model.Content +import org.matrix.android.sdk.api.session.room.model.message.LocationAsset +import org.matrix.android.sdk.api.session.room.model.message.LocationAssetType +import org.matrix.android.sdk.api.session.room.model.message.MessageContent +import org.matrix.android.sdk.api.session.room.model.message.MessageLiveLocationContent +import org.matrix.android.sdk.api.session.room.model.message.MessageType +import org.matrix.android.sdk.api.session.room.model.relation.RelationDefaultContent + +@JsonClass(generateAdapter = true) +data class LiveLocationBeaconContent( + /** + * Local message type, not from server + */ + @Transient + override val msgType: String = MessageType.MSGTYPE_LIVE_LOCATION_STATE, + + @Json(name = "body") override val body: String = "", + @Json(name = "m.relates_to") override val relatesTo: RelationDefaultContent? = null, + @Json(name = "m.new_content") override val newContent: Content? = null, + + /** + * Optional description of the beacon. + */ + @Json(name = "description") val description: String? = null, + /** + * Beacon should be considered as inactive after this timeout as milliseconds. + */ + @Json(name = "timeout") val timeout: Long? = null, + /** + * Should be set true to start sharing beacon. + */ + @Json(name = "live") val isLive: Boolean? = null, + + /** + * Beacon creation timestamp. + */ + @Json(name = "org.matrix.msc3488.ts") val unstableTimestampAsMilliseconds: Long? = null, + @Json(name = "m.ts") val timestampAsMilliseconds: Long? = null, + /** + * Live location asset type. + */ + @Json(name = "org.matrix.msc3488.asset") val unstableLocationAsset: LocationAsset = LocationAsset(LocationAssetType.SELF), + @Json(name = "m.asset") val locationAsset: LocationAsset? = null, + + /** + * Client side tracking of the last location + */ + var lastLocationContent: MessageLiveLocationContent? = null, + + /** + * Client side tracking of whether the beacon has timed out. + */ + var hasTimedOut: Boolean = false +) : MessageContent { + + fun getBestTimestampAsMilliseconds() = timestampAsMilliseconds ?: unstableTimestampAsMilliseconds + + fun getBestLocationAsset() = locationAsset ?: unstableLocationAsset +} diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/message/FileInfo.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/message/FileInfo.kt index f21074096e..132b72902f 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/message/FileInfo.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/message/FileInfo.kt @@ -18,7 +18,7 @@ package org.matrix.android.sdk.api.session.room.model.message import com.squareup.moshi.Json import com.squareup.moshi.JsonClass -import org.matrix.android.sdk.internal.crypto.model.rest.EncryptedFileInfo +import org.matrix.android.sdk.api.session.crypto.model.EncryptedFileInfo @JsonClass(generateAdapter = true) data class FileInfo( @@ -52,5 +52,5 @@ data class FileInfo( * Get the url of the encrypted thumbnail or of the thumbnail */ fun FileInfo.getThumbnailUrl(): String? { - return thumbnailFile?.url ?: thumbnailUrl + return thumbnailFile?.url ?: thumbnailUrl } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/message/ImageInfo.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/message/ImageInfo.kt index c38ef5bc27..bd99ea6900 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/message/ImageInfo.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/message/ImageInfo.kt @@ -18,7 +18,7 @@ package org.matrix.android.sdk.api.session.room.model.message import com.squareup.moshi.Json import com.squareup.moshi.JsonClass -import org.matrix.android.sdk.internal.crypto.model.rest.EncryptedFileInfo +import org.matrix.android.sdk.api.session.crypto.model.EncryptedFileInfo @JsonClass(generateAdapter = true) data class ImageInfo( @@ -62,5 +62,5 @@ data class ImageInfo( * Get the url of the encrypted thumbnail or of the thumbnail */ fun ImageInfo.getThumbnailUrl(): String? { - return thumbnailFile?.url ?: thumbnailUrl + return thumbnailFile?.url ?: thumbnailUrl } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/message/MessageAudioContent.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/message/MessageAudioContent.kt index ebf3d127ce..76a612b51b 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/message/MessageAudioContent.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/message/MessageAudioContent.kt @@ -18,10 +18,10 @@ package org.matrix.android.sdk.api.session.room.model.message import com.squareup.moshi.Json import com.squareup.moshi.JsonClass +import org.matrix.android.sdk.api.session.crypto.model.EncryptedFileInfo import org.matrix.android.sdk.api.session.events.model.Content import org.matrix.android.sdk.api.session.room.model.relation.RelationDefaultContent import org.matrix.android.sdk.api.util.JsonDict -import org.matrix.android.sdk.internal.crypto.model.rest.EncryptedFileInfo @JsonClass(generateAdapter = true) data class MessageAudioContent( diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/message/MessageFileContent.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/message/MessageFileContent.kt index 78f9a5d2f2..b5303e6c5d 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/message/MessageFileContent.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/message/MessageFileContent.kt @@ -19,9 +19,9 @@ package org.matrix.android.sdk.api.session.room.model.message import android.webkit.MimeTypeMap import com.squareup.moshi.Json import com.squareup.moshi.JsonClass +import org.matrix.android.sdk.api.session.crypto.model.EncryptedFileInfo import org.matrix.android.sdk.api.session.events.model.Content import org.matrix.android.sdk.api.session.room.model.relation.RelationDefaultContent -import org.matrix.android.sdk.internal.crypto.model.rest.EncryptedFileInfo @JsonClass(generateAdapter = true) data class MessageFileContent( diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/message/MessageImageContent.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/message/MessageImageContent.kt index ea7ab50688..f0caf52041 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/message/MessageImageContent.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/message/MessageImageContent.kt @@ -18,9 +18,9 @@ package org.matrix.android.sdk.api.session.room.model.message import com.squareup.moshi.Json import com.squareup.moshi.JsonClass +import org.matrix.android.sdk.api.session.crypto.model.EncryptedFileInfo import org.matrix.android.sdk.api.session.events.model.Content import org.matrix.android.sdk.api.session.room.model.relation.RelationDefaultContent -import org.matrix.android.sdk.internal.crypto.model.rest.EncryptedFileInfo @JsonClass(generateAdapter = true) data class MessageImageContent( diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/message/MessageLiveLocationContent.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/message/MessageLiveLocationContent.kt new file mode 100644 index 0000000000..548dc85369 --- /dev/null +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/message/MessageLiveLocationContent.kt @@ -0,0 +1,52 @@ +/* + * 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.room.model.message + +import com.squareup.moshi.Json +import com.squareup.moshi.JsonClass +import org.matrix.android.sdk.api.session.events.model.Content +import org.matrix.android.sdk.api.session.room.model.relation.RelationDefaultContent + +@JsonClass(generateAdapter = true) +data class MessageLiveLocationContent( + /** + * Local message type, not from server + */ + @Transient + override val msgType: String = MessageType.MSGTYPE_LIVE_LOCATION, + + @Json(name = "body") override val body: String = "", + @Json(name = "m.relates_to") override val relatesTo: RelationDefaultContent? = null, + @Json(name = "m.new_content") override val newContent: Content? = null, + + /** + * See [MSC3488](https://github.com/matrix-org/matrix-doc/blob/matthew/location/proposals/3488-location.md) + */ + @Json(name = "org.matrix.msc3488.location") val unstableLocationInfo: LocationInfo? = null, + @Json(name = "m.location") val locationInfo: LocationInfo? = null, + + /** + * Exact time that the data in the event refers to (milliseconds since the UNIX epoch) + */ + @Json(name = "org.matrix.msc3488.ts") val unstableTimestampAsMilliseconds: Long? = null, + @Json(name = "m.ts") val timestampAsMilliseconds: Long? = null +) : MessageContent { + + fun getBestLocationInfo() = locationInfo ?: unstableLocationInfo + + fun getBestTimestampAsMilliseconds() = timestampAsMilliseconds ?: unstableTimestampAsMilliseconds +} diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/message/MessageStickerContent.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/message/MessageStickerContent.kt index 8e1d4d3d75..3d774cadb2 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/message/MessageStickerContent.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/message/MessageStickerContent.kt @@ -18,9 +18,9 @@ package org.matrix.android.sdk.api.session.room.model.message import com.squareup.moshi.Json import com.squareup.moshi.JsonClass +import org.matrix.android.sdk.api.session.crypto.model.EncryptedFileInfo import org.matrix.android.sdk.api.session.events.model.Content import org.matrix.android.sdk.api.session.room.model.relation.RelationDefaultContent -import org.matrix.android.sdk.internal.crypto.model.rest.EncryptedFileInfo @JsonClass(generateAdapter = true) data class MessageStickerContent( diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/message/MessageType.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/message/MessageType.kt index 2a6138ae60..106bf2e030 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/message/MessageType.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/message/MessageType.kt @@ -33,10 +33,14 @@ object MessageType { const val MSGTYPE_STICKER_LOCAL = "org.matrix.android.sdk.sticker" // Fake message types for poll events to be able to inherit them from MessageContent - // Because poll events are not message events and they don't hanve msgtype field + // Because poll events are not message events and they don't have msgtype field const val MSGTYPE_POLL_START = "org.matrix.android.sdk.poll.start" const val MSGTYPE_POLL_RESPONSE = "org.matrix.android.sdk.poll.response" const val MSGTYPE_CONFETTI = "nic.custom.confetti" const val MSGTYPE_SNOWFALL = "io.element.effect.snowfall" + + // Fake message types for live location events to be able to inherit them from MessageContent + const val MSGTYPE_LIVE_LOCATION_STATE = "org.matrix.android.sdk.livelocation.state" + const val MSGTYPE_LIVE_LOCATION = "org.matrix.android.sdk.livelocation" } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/message/MessageVerificationRequestContent.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/message/MessageVerificationRequestContent.kt index b2b3cdac90..a0699831f7 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/message/MessageVerificationRequestContent.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/message/MessageVerificationRequestContent.kt @@ -24,7 +24,7 @@ import org.matrix.android.sdk.internal.crypto.verification.VerificationInfoReque @JsonClass(generateAdapter = true) data class MessageVerificationRequestContent( - @Json(name = MessageContent.MSG_TYPE_JSON_KEY)override val msgType: String = MessageType.MSGTYPE_VERIFICATION_REQUEST, + @Json(name = MessageContent.MSG_TYPE_JSON_KEY) override val msgType: String = MessageType.MSGTYPE_VERIFICATION_REQUEST, @Json(name = "body") override val body: String, @Json(name = "from_device") override val fromDevice: String?, @Json(name = "methods") override val methods: List, diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/message/MessageVideoContent.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/message/MessageVideoContent.kt index e1b0cd8607..9b657971b9 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/message/MessageVideoContent.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/message/MessageVideoContent.kt @@ -18,16 +18,16 @@ package org.matrix.android.sdk.api.session.room.model.message import com.squareup.moshi.Json import com.squareup.moshi.JsonClass +import org.matrix.android.sdk.api.session.crypto.model.EncryptedFileInfo import org.matrix.android.sdk.api.session.events.model.Content import org.matrix.android.sdk.api.session.room.model.relation.RelationDefaultContent -import org.matrix.android.sdk.internal.crypto.model.rest.EncryptedFileInfo @JsonClass(generateAdapter = true) data class MessageVideoContent( /** * Required. Must be 'm.video'. */ - @Json(name = MessageContent.MSG_TYPE_JSON_KEY)override val msgType: String, + @Json(name = MessageContent.MSG_TYPE_JSON_KEY) override val msgType: String, /** * Required. A description of the video e.g. 'Gangnam style', or some kind of content description for accessibility e.g. 'video attachment'. diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/message/MessageWithAttachmentContent.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/message/MessageWithAttachmentContent.kt index 7870db4f65..95dfb6b864 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/message/MessageWithAttachmentContent.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/message/MessageWithAttachmentContent.kt @@ -16,7 +16,7 @@ package org.matrix.android.sdk.api.session.room.model.message -import org.matrix.android.sdk.internal.crypto.model.rest.EncryptedFileInfo +import org.matrix.android.sdk.api.session.crypto.model.EncryptedFileInfo /** * Interface for message which can contains an encrypted file diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/message/VideoInfo.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/message/VideoInfo.kt index 8a36c26313..b02b4d96ad 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/message/VideoInfo.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/message/VideoInfo.kt @@ -18,7 +18,7 @@ package org.matrix.android.sdk.api.session.room.model.message import com.squareup.moshi.Json import com.squareup.moshi.JsonClass -import org.matrix.android.sdk.internal.crypto.model.rest.EncryptedFileInfo +import org.matrix.android.sdk.api.session.crypto.model.EncryptedFileInfo @JsonClass(generateAdapter = true) data class VideoInfo( @@ -67,5 +67,5 @@ data class VideoInfo( * Get the url of the encrypted thumbnail or of the thumbnail */ fun VideoInfo.getThumbnailUrl(): String? { - return thumbnailFile?.url ?: thumbnailUrl + return thumbnailFile?.url ?: thumbnailUrl } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/send/SendService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/send/SendService.kt index 9f8b1d93d7..af7ab11df1 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 @@ -146,6 +146,15 @@ interface SendService { */ 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/state/StateService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/state/StateService.kt index e9b0e4f676..f645f3ebf9 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 @@ -66,6 +66,19 @@ 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. diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/summary/RoomSummaryConstants.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/summary/RoomSummaryConstants.kt index eaed9053ea..8f214e0f89 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/summary/RoomSummaryConstants.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/summary/RoomSummaryConstants.kt @@ -33,5 +33,5 @@ object RoomSummaryConstants { EventType.ENCRYPTED, EventType.STICKER, EventType.REACTION - ) + EventType.POLL_START + ) + EventType.POLL_START + EventType.STATE_ROOM_BEACON_INFO } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/taggedevents/TaggedEventsContent.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/taggedevents/TaggedEventsContent.kt similarity index 97% rename from matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/taggedevents/TaggedEventsContent.kt rename to matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/taggedevents/TaggedEventsContent.kt index 1b19d27e1d..521a2315e4 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/taggedevents/TaggedEventsContent.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/taggedevents/TaggedEventsContent.kt @@ -14,7 +14,7 @@ * limitations under the License. */ -package org.matrix.android.sdk.internal.session.room.taggedevents +package org.matrix.android.sdk.api.session.room.taggedevents import com.squareup.moshi.Json import com.squareup.moshi.JsonClass 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 1b01efc074..a2ae8bfeb5 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/timeline/TimelineEvent.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/timeline/TimelineEvent.kt @@ -29,6 +29,7 @@ import org.matrix.android.sdk.api.session.events.model.isSticker import org.matrix.android.sdk.api.session.events.model.toModel import org.matrix.android.sdk.api.session.room.model.EventAnnotationsSummary import org.matrix.android.sdk.api.session.room.model.ReadReceipt +import org.matrix.android.sdk.api.session.room.model.livelocation.LiveLocationBeaconContent import org.matrix.android.sdk.api.session.room.model.message.MessageContent import org.matrix.android.sdk.api.session.room.model.message.MessagePollContent import org.matrix.android.sdk.api.session.room.model.message.MessageStickerContent @@ -55,7 +56,7 @@ data class TimelineEvent( * It's not unique on the timeline as it's reset on each chunk. */ val displayIndex: Int, - var ownedByThreadChunk: Boolean = false, + val ownedByThreadChunk: Boolean = false, val senderInfo: SenderInfo, val annotations: EventAnnotationsSummary? = null, val readReceipts: List = emptyList() @@ -136,9 +137,10 @@ 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() - else -> (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() + else -> (annotations?.editSummary?.latestContent ?: root.getClearContent()).toModel() } } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/timeline/TimelineEventFilters.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/timeline/TimelineEventFilters.kt index 4415c8e4b3..a35a291d9b 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/timeline/TimelineEventFilters.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/timeline/TimelineEventFilters.kt @@ -16,6 +16,7 @@ package org.matrix.android.sdk.api.session.room.timeline +// TODO Move to internal, strange? data class TimelineEventFilters( /** * A flag to filter edit events diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/timeline/TimelineSettings.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/timeline/TimelineSettings.kt index 6548453c8a..b45f3ecb71 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/timeline/TimelineSettings.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/timeline/TimelineSettings.kt @@ -31,7 +31,8 @@ data class TimelineSettings( /** * The root thread eventId if this is a thread timeline, or null if this is NOT a thread timeline */ - val rootThreadEventId: String? = null) { + val rootThreadEventId: String? = null, +) { /** * Returns true if this is a thread timeline or false otherwise diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/securestorage/SharedSecretStorageError.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/securestorage/SharedSecretStorageError.kt index a91b97b86c..038533c19e 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/securestorage/SharedSecretStorageError.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/securestorage/SharedSecretStorageError.kt @@ -23,7 +23,7 @@ sealed class SharedSecretStorageError(message: String?) : Throwable(message) { data class UnsupportedAlgorithm(val algorithm: String) : SharedSecretStorageError("Unknown algorithm $algorithm") data class SecretNotEncrypted(val secretName: String) : SharedSecretStorageError("Missing content for secret $secretName") data class SecretNotEncryptedWithKey(val secretName: String, val keyId: String) : - SharedSecretStorageError("Missing content for secret $secretName with key $keyId") + SharedSecretStorageError("Missing content for secret $secretName with key $keyId") object BadKeyFormat : SharedSecretStorageError("Bad Key Format") object ParsingError : SharedSecretStorageError("parsing Error") diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/securestorage/SsssKeyCreationInfo.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/securestorage/SsssKeyCreationInfo.kt index eeb1b31f9c..7a91a16c8c 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/securestorage/SsssKeyCreationInfo.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/securestorage/SsssKeyCreationInfo.kt @@ -18,7 +18,7 @@ package org.matrix.android.sdk.api.session.securestorage data class SsssKeyCreationInfo( val keyId: String = "", - var content: SecretStorageKeyContent?, + val content: SecretStorageKeyContent?, val recoveryKey: String = "", val keySpec: SsssKeySpec ) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/securestorage/SsssKeySpec.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/securestorage/SsssKeySpec.kt index f791ea4e86..03efb9b3db 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/securestorage/SsssKeySpec.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/securestorage/SsssKeySpec.kt @@ -17,8 +17,8 @@ package org.matrix.android.sdk.api.session.securestorage import org.matrix.android.sdk.api.listeners.ProgressListener +import org.matrix.android.sdk.api.session.crypto.keysbackup.extractCurveKeyFromRecoveryKey import org.matrix.android.sdk.internal.crypto.keysbackup.deriveKey -import org.matrix.android.sdk.internal.crypto.keysbackup.util.extractCurveKeyFromRecoveryKey /** Tag class */ interface SsssKeySpec 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 41c4e7eed1..afd26f7be5 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/space/SpaceService.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/space/SpaceService.kt @@ -22,7 +22,7 @@ import org.matrix.android.sdk.api.session.events.model.Event import org.matrix.android.sdk.api.session.room.RoomSortOrder import org.matrix.android.sdk.api.session.room.RoomSummaryQueryParams import org.matrix.android.sdk.api.session.room.model.RoomSummary -import org.matrix.android.sdk.internal.session.space.peeking.SpacePeekResult +import org.matrix.android.sdk.api.session.space.peeking.SpacePeekResult typealias SpaceSummaryQueryParams = RoomSummaryQueryParams @@ -68,7 +68,7 @@ interface SpaceService { suggestedOnly: Boolean? = null, limit: Int? = null, from: String? = null, - // when paginating, pass back the m.space.child state events + // when paginating, pass back the m.space.child state events knownStateList: List? = null): SpaceHierarchyData /** @@ -106,5 +106,8 @@ interface SpaceService { suspend fun removeSpaceParent(childRoomId: String, parentSpaceId: String) - fun getRootSpaceSummaries(): List + /** + * Get the root spaces, i.e. all the spaces which do not have a parent space. + */ + suspend fun getRootSpaceSummaries(): List } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/space/peeking/SpacePeekResult.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/space/peeking/SpacePeekResult.kt similarity index 88% rename from matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/space/peeking/SpacePeekResult.kt rename to matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/space/peeking/SpacePeekResult.kt index a2ffd8221a..06dbd12d7a 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/space/peeking/SpacePeekResult.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/space/peeking/SpacePeekResult.kt @@ -14,11 +14,18 @@ * limitations under the License. */ -package org.matrix.android.sdk.internal.session.space.peeking +package org.matrix.android.sdk.api.session.space.peeking import org.matrix.android.sdk.api.session.room.peeking.PeekResult -// TODO Move to api package +sealed class SpacePeekResult { + abstract class SpacePeekError : SpacePeekResult() + data class FailedToResolve(val spaceId: String, val roomPeekResult: PeekResult) : SpacePeekError() + data class NotSpaceType(val spaceId: String) : SpacePeekError() + + data class Success(val summary: SpacePeekSummary) : SpacePeekResult() +} + data class SpacePeekSummary( val idOrAlias: String, val roomPeekResult: PeekResult.Success, @@ -28,30 +35,18 @@ data class SpacePeekSummary( interface ISpaceChild { val id: String val roomPeekResult: PeekResult - -// val default: Boolean? val order: String? } data class SpaceChildPeekResult( override val id: String, override val roomPeekResult: PeekResult, -// override val default: Boolean? = null, override val order: String? = null ) : ISpaceChild data class SpaceSubChildPeekResult( override val id: String, override val roomPeekResult: PeekResult, -// override val default: Boolean?, override val order: String?, val children: List ) : ISpaceChild - -sealed class SpacePeekResult { - abstract class SpacePeekError : SpacePeekResult() - data class FailedToResolve(val spaceId: String, val roomPeekResult: PeekResult) : SpacePeekError() - data class NotSpaceType(val spaceId: String) : SpacePeekError() - - data class Success(val summary: SpacePeekSummary) : SpacePeekResult() -} diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/InitialSyncStrategy.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/sync/InitialSyncStrategy.kt similarity index 94% rename from matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/InitialSyncStrategy.kt rename to matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/sync/InitialSyncStrategy.kt index 4bc866b36d..461d816ea7 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/InitialSyncStrategy.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/sync/InitialSyncStrategy.kt @@ -1,5 +1,5 @@ /* - * Copyright 2020 The Matrix.org Foundation C.I.C. + * Copyright (c) 2022 The Matrix.org Foundation C.I.C. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -14,7 +14,7 @@ * limitations under the License. */ -package org.matrix.android.sdk.internal.session.sync +package org.matrix.android.sdk.api.session.sync var initialSyncStrategy: InitialSyncStrategy = InitialSyncStrategy.Optimized() diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/job/SyncService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/sync/job/SyncService.kt similarity index 98% rename from matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/job/SyncService.kt rename to matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/sync/job/SyncService.kt index 97ae9b3a68..ac81be2174 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/job/SyncService.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/sync/job/SyncService.kt @@ -1,11 +1,11 @@ /* - * Copyright 2020 The Matrix.org Foundation C.I.C. + * Copyright (c) 2022 The Matrix.org Foundation C.I.C. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * - * http://www.apache.org/licenses/LICENSE-2.0 + * 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, @@ -13,7 +13,8 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.matrix.android.sdk.internal.session.sync.job + +package org.matrix.android.sdk.api.session.sync.job import android.app.Service import android.content.Intent diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/terms/GetTermsResponse.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/terms/GetTermsResponse.kt index e6d33cade6..b7bdc2b770 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/terms/GetTermsResponse.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/terms/GetTermsResponse.kt @@ -16,8 +16,6 @@ package org.matrix.android.sdk.api.session.terms -import org.matrix.android.sdk.internal.session.terms.TermsResponse - data class GetTermsResponse( val serverResponse: TermsResponse, val alreadyAcceptedTermUrls: Set diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/terms/TermsResponse.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/terms/TermsResponse.kt similarity index 93% rename from matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/terms/TermsResponse.kt rename to matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/terms/TermsResponse.kt index a185e0b80f..9a30b4d764 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/terms/TermsResponse.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/terms/TermsResponse.kt @@ -14,12 +14,12 @@ * limitations under the License. */ -package org.matrix.android.sdk.internal.session.terms +package org.matrix.android.sdk.api.session.terms import com.squareup.moshi.Json import com.squareup.moshi.JsonClass +import org.matrix.android.sdk.api.auth.data.LocalizedFlowDataLoginTerms import org.matrix.android.sdk.api.util.JsonDict -import org.matrix.android.sdk.internal.auth.registration.LocalizedFlowDataLoginTerms /** * This class represent a localized privacy policy for registration Flow. 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 e64cf1872e..6c357b2224 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 @@ -16,8 +16,6 @@ package org.matrix.android.sdk.api.session.terms -import org.matrix.android.sdk.internal.session.terms.TermsResponse - interface TermsService { enum class ServiceType { IntegrationManager, diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/threads/ThreadDetails.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/threads/ThreadDetails.kt index d6937d5b26..c8fe1c85ea 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/threads/ThreadDetails.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/threads/ThreadDetails.kt @@ -29,7 +29,7 @@ data class ThreadDetails( val threadSummarySenderInfo: SenderInfo? = null, val threadSummaryLatestEvent: Event? = null, val lastMessageTimestamp: Long? = null, - var threadNotificationState: ThreadNotificationState = ThreadNotificationState.NO_NEW_MESSAGE, + val threadNotificationState: ThreadNotificationState = ThreadNotificationState.NO_NEW_MESSAGE, val isThread: Boolean = false, val lastRootThreadEdition: String? = null ) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/model/rest/DefaultBaseAuth.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/uia/DefaultBaseAuth.kt similarity index 91% rename from matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/model/rest/DefaultBaseAuth.kt rename to matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/uia/DefaultBaseAuth.kt index bbb4a3a654..865e02daf2 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/model/rest/DefaultBaseAuth.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/uia/DefaultBaseAuth.kt @@ -1,5 +1,5 @@ /* - * Copyright 2020 The Matrix.org Foundation C.I.C. + * Copyright (c) 2022 The Matrix.org Foundation C.I.C. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -14,7 +14,7 @@ * limitations under the License. */ -package org.matrix.android.sdk.internal.crypto.model.rest +package org.matrix.android.sdk.api.session.uia import org.matrix.android.sdk.api.auth.UIABaseAuth diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/data/InteractiveAuthenticationFlow.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/uia/InteractiveAuthenticationFlow.kt similarity index 94% rename from matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/data/InteractiveAuthenticationFlow.kt rename to matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/uia/InteractiveAuthenticationFlow.kt index d66bcfb274..a78c047221 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/data/InteractiveAuthenticationFlow.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/uia/InteractiveAuthenticationFlow.kt @@ -14,7 +14,7 @@ * limitations under the License. */ -package org.matrix.android.sdk.internal.auth.data +package org.matrix.android.sdk.api.session.uia import com.squareup.moshi.Json import com.squareup.moshi.JsonClass diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/user/UserService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/user/UserService.kt index cd4fb216d3..063abdb5a0 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/user/UserService.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/user/UserService.kt @@ -75,11 +75,14 @@ interface UserService { /** * Ignore users + * Note: once done, for the change to take effect, you have to request an initial sync. + * This may be improved in the future */ suspend fun ignoreUserIds(userIds: List) /** * Un-ignore some users + * Note: once done, for the change to take effect, you have to request an initial sync. */ suspend fun unIgnoreUserIds(userIds: List) } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/user/model/User.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/user/model/User.kt index 54ae9e54f6..79c86f3f23 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/user/model/User.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/user/model/User.kt @@ -16,6 +16,9 @@ package org.matrix.android.sdk.api.session.user.model +import org.matrix.android.sdk.api.session.profile.ProfileService +import org.matrix.android.sdk.api.util.JsonDict + /** * Data class which holds information about a user. * It can be retrieved with [org.matrix.android.sdk.api.session.user.UserService] @@ -27,4 +30,14 @@ data class User( */ val displayName: String? = null, val avatarUrl: String? = null -) +) { + + companion object { + + fun fromJson(userId: String, json: JsonDict) = User( + userId = userId, + displayName = json[ProfileService.DISPLAY_NAME_KEY] as? String, + avatarUrl = json[ProfileService.AVATAR_URL_KEY] as? String + ) + } +} diff --git a/vector/src/main/java/im/vector/app/core/extensions/Exhaustive.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/settings/LightweightSettingsStorage.kt similarity index 70% rename from vector/src/main/java/im/vector/app/core/extensions/Exhaustive.kt rename to matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/settings/LightweightSettingsStorage.kt index 158ea84f0c..8dd20a702b 100644 --- a/vector/src/main/java/im/vector/app/core/extensions/Exhaustive.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/settings/LightweightSettingsStorage.kt @@ -1,5 +1,5 @@ /* - * Copyright 2020 New Vector Ltd + * Copyright (c) 2022 The Matrix.org Foundation C.I.C. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -14,7 +14,9 @@ * limitations under the License. */ -package im.vector.app.core.extensions +package org.matrix.android.sdk.api.settings -// Trick to ensure that when block is exhaustive -val T.exhaustive: T get() = this +interface LightweightSettingsStorage { + fun setThreadMessagesEnabled(enabled: Boolean) + fun areThreadMessagesEnabled(): Boolean +} diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/util/Base64.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/util/Base64.kt new file mode 100644 index 0000000000..e0596c1325 --- /dev/null +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/util/Base64.kt @@ -0,0 +1,27 @@ +/* + * Copyright (c) 2022 The Matrix.org Foundation C.I.C. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.matrix.android.sdk.api.util + +import android.util.Base64 + +fun ByteArray.toBase64NoPadding(): String { + return Base64.encodeToString(this, Base64.NO_PADDING or Base64.NO_WRAP) +} + +fun String.fromBase64(): ByteArray { + return Base64.decode(this, Base64.DEFAULT) +} diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/util/Hash.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/util/Hash.kt similarity index 95% rename from matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/util/Hash.kt rename to matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/util/Hash.kt index 47f20913ec..7465eed3ae 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/util/Hash.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/util/Hash.kt @@ -14,7 +14,7 @@ * limitations under the License. */ -package org.matrix.android.sdk.internal.util +package org.matrix.android.sdk.api.util import java.security.MessageDigest import java.util.Locale 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 650b8cc26d..4f5f4f82d9 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 @@ -37,6 +37,7 @@ sealed class MatrixItem( override val displayName: String? = null, override val avatarUrl: String? = null) : MatrixItem(id, displayName?.removeSuffix(IRC_PATTERN), avatarUrl) { + init { if (BuildConfig.DEBUG) checkId() } @@ -200,7 +201,7 @@ fun RoomMemberSummary.toMatrixItem() = MatrixItem.UserItem(userId, displayName, fun SenderInfo.toMatrixItem() = MatrixItem.UserItem(userId, disambiguatedDisplayName, avatarUrl) -fun SenderInfo.toMatrixItemOrNull() = tryOrNull { MatrixItem.UserItem(userId, disambiguatedDisplayName, avatarUrl) } +fun SenderInfo.toMatrixItemOrNull() = tryOrNull { MatrixItem.UserItem(userId, disambiguatedDisplayName, avatarUrl) } fun SpaceChildInfo.toMatrixItem() = if (roomType == RoomType.SPACE) { MatrixItem.SpaceItem(childRoomId, name ?: canonicalAlias, avatarUrl) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/util/MatrixJsonParser.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/util/MatrixJsonParser.kt new file mode 100644 index 0000000000..48a41667b2 --- /dev/null +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/util/MatrixJsonParser.kt @@ -0,0 +1,32 @@ +/* + * 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.util + +import com.squareup.moshi.Moshi +import org.matrix.android.sdk.internal.di.MoshiProvider + +/** + * Entry point to get a Json parser + */ +object MatrixJsonParser { + /** + * @return a Moshi Json parser instance, configured to handle some Matrix Event contents + */ + fun getMoshi(): Moshi { + return MoshiProvider.providesMoshi() + } +} diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/util/SuspendMatrixCallback.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/util/SuspendMatrixCallback.kt similarity index 96% rename from matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/util/SuspendMatrixCallback.kt rename to matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/util/SuspendMatrixCallback.kt index 145fc92fea..381dfb65fa 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/util/SuspendMatrixCallback.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/util/SuspendMatrixCallback.kt @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.matrix.android.sdk.internal.util +package org.matrix.android.sdk.api.util import org.matrix.android.sdk.api.MatrixCallback import kotlin.coroutines.resume diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/util/TextContent.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/util/TextContent.kt new file mode 100644 index 0000000000..fe12d7b1cf --- /dev/null +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/util/TextContent.kt @@ -0,0 +1,27 @@ +/* + * Copyright (c) 2022 The Matrix.org Foundation C.I.C. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.matrix.android.sdk.api.util + +/** + * Contains a text and eventually a formatted text + */ +data class TextContent( + val text: String, + val formattedText: String? = null +) { + fun takeFormatted() = formattedText ?: text +} diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/AuthAPI.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/AuthAPI.kt index 554e21ce55..ebad859b05 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/AuthAPI.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/AuthAPI.kt @@ -26,6 +26,7 @@ import org.matrix.android.sdk.internal.auth.data.WebClientConfig import org.matrix.android.sdk.internal.auth.login.ResetPasswordMailConfirmed import org.matrix.android.sdk.internal.auth.registration.AddThreePidRegistrationParams import org.matrix.android.sdk.internal.auth.registration.AddThreePidRegistrationResponse +import org.matrix.android.sdk.internal.auth.registration.RegistrationCustomParams import org.matrix.android.sdk.internal.auth.registration.RegistrationParams import org.matrix.android.sdk.internal.auth.registration.SuccessResult import org.matrix.android.sdk.internal.auth.registration.ValidationCodeBody @@ -68,6 +69,14 @@ internal interface AuthAPI { @POST(NetworkConstants.URI_API_PREFIX_PATH_R0 + "register") suspend fun register(@Body registrationParams: RegistrationParams): Credentials + /** + * Register to the homeserver, or get error 401 with a RegistrationFlowResponse object if registration is incomplete + * method to perform other custom stages + * Ref: https://matrix.org/docs/spec/client_server/latest#account-registration-and-management + */ + @POST(NetworkConstants.URI_API_PREFIX_PATH_R0 + "register") + suspend fun registerCustom(@Body registrationCustomParams: RegistrationCustomParams): Credentials + /** * Checks to see if a username is available, and valid, for the server. */ diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/DefaultHomeServerHistoryService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/DefaultHomeServerHistoryService.kt index 7415938ebc..c57ce6875d 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/DefaultHomeServerHistoryService.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/DefaultHomeServerHistoryService.kt @@ -23,7 +23,7 @@ import org.matrix.android.sdk.internal.database.model.KnownServerUrlEntity import org.matrix.android.sdk.internal.di.GlobalDatabase import javax.inject.Inject -class DefaultHomeServerHistoryService @Inject constructor( +internal class DefaultHomeServerHistoryService @Inject constructor( @GlobalDatabase private val monarchy: Monarchy ) : HomeServerHistoryService { diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/db/migration/MigrateAuthTo001.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/db/migration/MigrateAuthTo001.kt index 627f4e16bc..a8d5e29689 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/db/migration/MigrateAuthTo001.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/db/migration/MigrateAuthTo001.kt @@ -21,7 +21,7 @@ import org.matrix.android.sdk.internal.auth.db.PendingSessionEntityFields import org.matrix.android.sdk.internal.util.database.RealmMigrator import timber.log.Timber -class MigrateAuthTo001(realm: DynamicRealm) : RealmMigrator(realm, 1) { +internal class MigrateAuthTo001(realm: DynamicRealm) : RealmMigrator(realm, 1) { override fun doMigrate(realm: DynamicRealm) { Timber.d("Create PendingSessionEntity") diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/db/migration/MigrateAuthTo002.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/db/migration/MigrateAuthTo002.kt index 6b133f8580..ef3a3e7f9b 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/db/migration/MigrateAuthTo002.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/db/migration/MigrateAuthTo002.kt @@ -21,7 +21,7 @@ import org.matrix.android.sdk.internal.auth.db.SessionParamsEntityFields import org.matrix.android.sdk.internal.util.database.RealmMigrator import timber.log.Timber -class MigrateAuthTo002(realm: DynamicRealm) : RealmMigrator(realm, 2) { +internal class MigrateAuthTo002(realm: DynamicRealm) : RealmMigrator(realm, 2) { override fun doMigrate(realm: DynamicRealm) { Timber.d("Add boolean isTokenValid in SessionParamsEntity, with value true") diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/db/migration/MigrateAuthTo003.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/db/migration/MigrateAuthTo003.kt index 9319ec9987..2584df1895 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/db/migration/MigrateAuthTo003.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/db/migration/MigrateAuthTo003.kt @@ -24,7 +24,7 @@ import org.matrix.android.sdk.internal.di.MoshiProvider import org.matrix.android.sdk.internal.util.database.RealmMigrator import timber.log.Timber -class MigrateAuthTo003(realm: DynamicRealm) : RealmMigrator(realm, 3) { +internal class MigrateAuthTo003(realm: DynamicRealm) : RealmMigrator(realm, 3) { override fun doMigrate(realm: DynamicRealm) { Timber.d("Update SessionParamsEntity primary key, to allow several sessions with the same userId") diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/db/migration/MigrateAuthTo004.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/db/migration/MigrateAuthTo004.kt index 4a9b9022d5..6dfec6a1aa 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/db/migration/MigrateAuthTo004.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/db/migration/MigrateAuthTo004.kt @@ -24,7 +24,7 @@ import org.matrix.android.sdk.internal.di.MoshiProvider import org.matrix.android.sdk.internal.util.database.RealmMigrator import timber.log.Timber -class MigrateAuthTo004(realm: DynamicRealm) : RealmMigrator(realm, 4) { +internal class MigrateAuthTo004(realm: DynamicRealm) : RealmMigrator(realm, 4) { override fun doMigrate(realm: DynamicRealm) { Timber.d("Update SessionParamsEntity to add HomeServerConnectionConfig.homeServerUriBase value") diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/registration/AuthParams.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/registration/AuthParams.kt index 23fdbc613a..cb17207741 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/registration/AuthParams.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/registration/AuthParams.kt @@ -88,15 +88,3 @@ internal data class AuthParams( } } } - -@JsonClass(generateAdapter = true) -data class ThreePidCredentials( - @Json(name = "client_secret") - val clientSecret: String? = null, - - @Json(name = "id_server") - val idServer: String? = null, - - @Json(name = "sid") - val sid: String? = null -) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/registration/DefaultRegistrationWizard.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/registration/DefaultRegistrationWizard.kt index 4a156e74cd..590b333e90 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/registration/DefaultRegistrationWizard.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/registration/DefaultRegistrationWizard.kt @@ -17,6 +17,7 @@ package org.matrix.android.sdk.internal.auth.registration import kotlinx.coroutines.delay +import org.matrix.android.sdk.api.auth.data.Credentials import org.matrix.android.sdk.api.auth.data.LoginFlowTypes import org.matrix.android.sdk.api.auth.registration.RegisterThreePid import org.matrix.android.sdk.api.auth.registration.RegistrationAvailability @@ -25,6 +26,7 @@ import org.matrix.android.sdk.api.auth.registration.RegistrationWizard import org.matrix.android.sdk.api.auth.registration.toFlowResult import org.matrix.android.sdk.api.failure.Failure import org.matrix.android.sdk.api.failure.Failure.RegistrationFlowError +import org.matrix.android.sdk.api.util.JsonDict import org.matrix.android.sdk.internal.auth.AuthAPI import org.matrix.android.sdk.internal.auth.PendingSessionStore import org.matrix.android.sdk.internal.auth.SessionCreator @@ -45,6 +47,7 @@ internal class DefaultRegistrationWizard( private val registerAvailableTask: RegisterAvailableTask = DefaultRegisterAvailableTask(authAPI) private val registerAddThreePidTask: RegisterAddThreePidTask = DefaultRegisterAddThreePidTask(authAPI) private val validateCodeTask: ValidateCodeTask = DefaultValidateCodeTask(authAPI) + private val registerCustomTask: RegisterCustomTask = DefaultRegisterCustomTask(authAPI) override val currentThreePid: String? get() { @@ -187,22 +190,51 @@ internal class DefaultRegistrationWizard( return performRegistrationRequest(params) } - private suspend fun performRegistrationRequest(registrationParams: RegistrationParams, - delayMillis: Long = 0): RegistrationResult { + override suspend fun registrationCustom( + authParams: JsonDict + ): RegistrationResult { + val safeSession = pendingSessionData.currentSession + ?: throw IllegalStateException("developer error, call createAccount() method first") + + val mutableParams = authParams.toMutableMap() + mutableParams["session"] = safeSession + + val params = RegistrationCustomParams(auth = mutableParams) + return performRegistrationOtherRequest(params) + } + + private suspend fun performRegistrationRequest( + registrationParams: RegistrationParams, + delayMillis: Long = 0 + ): RegistrationResult { delay(delayMillis) + return register { registerTask.execute(RegisterTask.Params(registrationParams)) } + } + + private suspend fun performRegistrationOtherRequest( + registrationCustomParams: RegistrationCustomParams + ): RegistrationResult { + return register { registerCustomTask.execute(RegisterCustomTask.Params(registrationCustomParams)) } + } + + private suspend fun register( + execute: suspend () -> Credentials + ): RegistrationResult { val credentials = try { - registerTask.execute(RegisterTask.Params(registrationParams)) + execute.invoke() } catch (exception: Throwable) { if (exception is RegistrationFlowError) { - pendingSessionData = pendingSessionData.copy(currentSession = exception.registrationFlowResponse.session) - .also { pendingSessionStore.savePendingSessionData(it) } + pendingSessionData = + pendingSessionData.copy(currentSession = exception.registrationFlowResponse.session) + .also { pendingSessionStore.savePendingSessionData(it) } return RegistrationResult.FlowResponse(exception.registrationFlowResponse.toFlowResult()) } else { throw exception } } - val session = sessionCreator.createSession(credentials, pendingSessionData.homeServerConnectionConfig) + val session = + sessionCreator.createSession(credentials, pendingSessionData.homeServerConnectionConfig) return RegistrationResult.Success(session) } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/registration/RegisterCustomTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/registration/RegisterCustomTask.kt new file mode 100644 index 0000000000..60af708c38 --- /dev/null +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/registration/RegisterCustomTask.kt @@ -0,0 +1,47 @@ +/* + * Copyright 2021 The Matrix.org Foundation C.I.C. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.matrix.android.sdk.internal.auth.registration + +import org.matrix.android.sdk.api.auth.data.Credentials +import org.matrix.android.sdk.api.failure.Failure +import org.matrix.android.sdk.api.failure.toRegistrationFlowResponse +import org.matrix.android.sdk.internal.auth.AuthAPI +import org.matrix.android.sdk.internal.network.executeRequest +import org.matrix.android.sdk.internal.task.Task + +internal interface RegisterCustomTask : Task { + data class Params( + val registrationCustomParams: RegistrationCustomParams + ) +} + +internal class DefaultRegisterCustomTask( + private val authAPI: AuthAPI +) : RegisterCustomTask { + + override suspend fun execute(params: RegisterCustomTask.Params): Credentials { + try { + return executeRequest(null) { + authAPI.registerCustom(params.registrationCustomParams) + } + } catch (throwable: Throwable) { + throw throwable.toRegistrationFlowResponse() + ?.let { Failure.RegistrationFlowError(it) } + ?: throwable + } + } +} diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/registration/RegistrationCustomParams.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/registration/RegistrationCustomParams.kt new file mode 100644 index 0000000000..45adac6c26 --- /dev/null +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/registration/RegistrationCustomParams.kt @@ -0,0 +1,31 @@ +/* + * Copyright 2021 The Matrix.org Foundation C.I.C. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.matrix.android.sdk.internal.auth.registration + +import com.squareup.moshi.Json +import com.squareup.moshi.JsonClass +import org.matrix.android.sdk.api.util.JsonDict + +/** + * Class to pass parameters to the custom registration types for /register. + */ +@JsonClass(generateAdapter = true) +internal data class RegistrationCustomParams( + // authentication parameters + @Json(name = "auth") + val auth: JsonDict? = null, +) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/registration/SuccessResult.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/registration/SuccessResult.kt index 9b158cce90..c666eec749 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/registration/SuccessResult.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/registration/SuccessResult.kt @@ -21,7 +21,7 @@ import com.squareup.moshi.JsonClass import org.matrix.android.sdk.api.extensions.orFalse @JsonClass(generateAdapter = true) -data class SuccessResult( +internal data class SuccessResult( @Json(name = "success") val success: Boolean? ) { diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/model/event/NewDeviceContent.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/registration/ThreePidCredentials.kt similarity index 64% rename from matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/model/event/NewDeviceContent.kt rename to matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/registration/ThreePidCredentials.kt index 2a63b4bee8..296ccadf91 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/model/event/NewDeviceContent.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/registration/ThreePidCredentials.kt @@ -1,5 +1,5 @@ /* - * Copyright 2020 The Matrix.org Foundation C.I.C. + * Copyright (c) 2022 The Matrix.org Foundation C.I.C. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -13,18 +13,20 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.matrix.android.sdk.internal.crypto.model.event + +package org.matrix.android.sdk.internal.auth.registration import com.squareup.moshi.Json import com.squareup.moshi.JsonClass @JsonClass(generateAdapter = true) -data class NewDeviceContent( - // the device id - @Json(name = "device_id") - val deviceId: String? = null, +internal data class ThreePidCredentials( + @Json(name = "client_secret") + val clientSecret: String? = null, - // the room ids list - @Json(name = "rooms") - val rooms: List? = null + @Json(name = "id_server") + val idServer: String? = null, + + @Json(name = "sid") + val sid: String? = null ) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/registration/ValidationCodeBody.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/registration/ValidationCodeBody.kt index b6f3e83929..ae71ae3a08 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/registration/ValidationCodeBody.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/registration/ValidationCodeBody.kt @@ -23,7 +23,7 @@ import com.squareup.moshi.JsonClass * This object is used to send a code received by SMS to validate Msisdn ownership */ @JsonClass(generateAdapter = true) -data class ValidationCodeBody( +internal data class ValidationCodeBody( @Json(name = "client_secret") val clientSecret: String, diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/CancelGossipRequestWorker.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/CancelGossipRequestWorker.kt index 3a5f8e7668..4380e31bff 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/CancelGossipRequestWorker.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/CancelGossipRequestWorker.kt @@ -21,11 +21,12 @@ import androidx.work.WorkerParameters import com.squareup.moshi.JsonClass import org.matrix.android.sdk.api.auth.data.Credentials import org.matrix.android.sdk.api.failure.shouldBeRetried +import org.matrix.android.sdk.api.session.crypto.model.MXUsersDevicesMap +import org.matrix.android.sdk.api.session.crypto.model.OutgoingGossipingRequestState import org.matrix.android.sdk.api.session.events.model.Event import org.matrix.android.sdk.api.session.events.model.EventType import org.matrix.android.sdk.api.session.events.model.toContent import org.matrix.android.sdk.internal.SessionManager -import org.matrix.android.sdk.internal.crypto.model.MXUsersDevicesMap import org.matrix.android.sdk.internal.crypto.model.rest.ShareRequestCancellation import org.matrix.android.sdk.internal.crypto.store.IMXCryptoStore import org.matrix.android.sdk.internal.crypto.tasks.SendToDeviceTask @@ -36,7 +37,7 @@ import org.matrix.android.sdk.internal.worker.SessionWorkerParams import javax.inject.Inject internal class CancelGossipRequestWorker(context: Context, params: WorkerParameters, sessionManager: SessionManager) : - SessionSafeCoroutineWorker(context, params, sessionManager, Params::class.java) { + SessionSafeCoroutineWorker(context, params, sessionManager, Params::class.java) { @JsonClass(generateAdapter = true) internal data class Params( 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 3130a6382f..2265526484 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 @@ -137,8 +137,7 @@ internal abstract class CryptoModule { @JvmStatic @Provides @CryptoDatabase - fun providesClearCacheTask(@CryptoDatabase - realmConfiguration: RealmConfiguration): ClearCacheTask { + fun providesClearCacheTask(@CryptoDatabase realmConfiguration: RealmConfiguration): ClearCacheTask { return RealmClearCacheTask(realmConfiguration) } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/CryptoSessionInfoProvider.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/CryptoSessionInfoProvider.kt index 2a58d731e5..73dfc468d9 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/CryptoSessionInfoProvider.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/CryptoSessionInfoProvider.kt @@ -48,7 +48,7 @@ internal class CryptoSessionInfoProvider @Inject constructor( /** * @param allActive if true return joined as well as invited, if false, only joined */ - fun getRoomUserIds(roomId: String, allActive: Boolean): List { + fun getRoomUserIds(roomId: String, allActive: Boolean): List { var userIds: List = emptyList() monarchy.doWithRealm { realm -> userIds = if (allActive) { diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/DefaultCryptoService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/DefaultCryptoService.kt index db44abc36f..6a57d94677 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 @@ -32,6 +32,8 @@ import org.matrix.android.sdk.api.MatrixCallback import org.matrix.android.sdk.api.MatrixCoroutineDispatchers import org.matrix.android.sdk.api.NoOpMatrixCallback import org.matrix.android.sdk.api.auth.UserInteractiveAuthInterceptor +import org.matrix.android.sdk.api.crypto.MXCRYPTO_ALGORITHM_MEGOLM +import org.matrix.android.sdk.api.crypto.MXCRYPTO_ALGORITHM_OLM import org.matrix.android.sdk.api.crypto.MXCryptoConfig import org.matrix.android.sdk.api.extensions.tryOrNull import org.matrix.android.sdk.api.failure.Failure @@ -39,14 +41,31 @@ import org.matrix.android.sdk.api.listeners.ProgressListener import org.matrix.android.sdk.api.logger.LoggerTag import org.matrix.android.sdk.api.session.crypto.CryptoService import org.matrix.android.sdk.api.session.crypto.MXCryptoError +import org.matrix.android.sdk.api.session.crypto.NewSessionListener +import org.matrix.android.sdk.api.session.crypto.crosssigning.DeviceTrustLevel import org.matrix.android.sdk.api.session.crypto.crosssigning.KEYBACKUP_SECRET_SSSS_NAME import org.matrix.android.sdk.api.session.crypto.crosssigning.MASTER_KEY_SSSS_NAME import org.matrix.android.sdk.api.session.crypto.crosssigning.SELF_SIGNING_KEY_SSSS_NAME import org.matrix.android.sdk.api.session.crypto.crosssigning.USER_SIGNING_KEY_SSSS_NAME import org.matrix.android.sdk.api.session.crypto.keyshare.GossipingRequestListener +import org.matrix.android.sdk.api.session.crypto.model.CryptoDeviceInfo +import org.matrix.android.sdk.api.session.crypto.model.DeviceInfo +import org.matrix.android.sdk.api.session.crypto.model.DevicesListResponse +import org.matrix.android.sdk.api.session.crypto.model.ImportRoomKeysResult +import org.matrix.android.sdk.api.session.crypto.model.IncomingRoomKeyRequest +import org.matrix.android.sdk.api.session.crypto.model.MXDeviceInfo +import org.matrix.android.sdk.api.session.crypto.model.MXEncryptEventContentResult +import org.matrix.android.sdk.api.session.crypto.model.MXEventDecryptionResult +import org.matrix.android.sdk.api.session.crypto.model.MXUsersDevicesMap +import org.matrix.android.sdk.api.session.crypto.model.OutgoingRoomKeyRequest +import org.matrix.android.sdk.api.session.crypto.model.RoomKeyRequestBody import org.matrix.android.sdk.api.session.events.model.Content import org.matrix.android.sdk.api.session.events.model.Event import org.matrix.android.sdk.api.session.events.model.EventType +import org.matrix.android.sdk.api.session.events.model.content.EncryptedEventContent +import org.matrix.android.sdk.api.session.events.model.content.RoomKeyContent +import org.matrix.android.sdk.api.session.events.model.content.RoomKeyWithHeldContent +import org.matrix.android.sdk.api.session.events.model.content.SecretSendEventContent import org.matrix.android.sdk.api.session.events.model.toModel import org.matrix.android.sdk.api.session.room.model.Membership import org.matrix.android.sdk.api.session.room.model.RoomHistoryVisibility @@ -61,21 +80,8 @@ import org.matrix.android.sdk.internal.crypto.algorithms.IMXWithHeldExtension import org.matrix.android.sdk.internal.crypto.algorithms.megolm.MXMegolmEncryptionFactory import org.matrix.android.sdk.internal.crypto.algorithms.olm.MXOlmEncryptionFactory import org.matrix.android.sdk.internal.crypto.crosssigning.DefaultCrossSigningService -import org.matrix.android.sdk.internal.crypto.crosssigning.DeviceTrustLevel import org.matrix.android.sdk.internal.crypto.keysbackup.DefaultKeysBackupService -import org.matrix.android.sdk.internal.crypto.model.CryptoDeviceInfo -import org.matrix.android.sdk.internal.crypto.model.ImportRoomKeysResult -import org.matrix.android.sdk.internal.crypto.model.MXDeviceInfo -import org.matrix.android.sdk.internal.crypto.model.MXEncryptEventContentResult import org.matrix.android.sdk.internal.crypto.model.MXKey.Companion.KEY_SIGNED_CURVE_25519_TYPE -import org.matrix.android.sdk.internal.crypto.model.MXUsersDevicesMap -import org.matrix.android.sdk.internal.crypto.model.event.EncryptedEventContent -import org.matrix.android.sdk.internal.crypto.model.event.RoomKeyContent -import org.matrix.android.sdk.internal.crypto.model.event.RoomKeyWithHeldContent -import org.matrix.android.sdk.internal.crypto.model.event.SecretSendEventContent -import org.matrix.android.sdk.internal.crypto.model.rest.DeviceInfo -import org.matrix.android.sdk.internal.crypto.model.rest.DevicesListResponse -import org.matrix.android.sdk.internal.crypto.model.rest.RoomKeyRequestBody import org.matrix.android.sdk.internal.crypto.model.toRest import org.matrix.android.sdk.internal.crypto.repository.WarnOnUnknownDeviceRepository import org.matrix.android.sdk.internal.crypto.store.IMXCryptoStore 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 494e6d7cc7..6cae2d0935 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 @@ -21,10 +21,10 @@ import kotlinx.coroutines.launch import org.matrix.android.sdk.api.MatrixCoroutineDispatchers import org.matrix.android.sdk.api.MatrixPatterns import org.matrix.android.sdk.api.auth.data.Credentials -import org.matrix.android.sdk.internal.crypto.crosssigning.DeviceTrustLevel -import org.matrix.android.sdk.internal.crypto.model.CryptoDeviceInfo +import org.matrix.android.sdk.api.session.crypto.crosssigning.DeviceTrustLevel +import org.matrix.android.sdk.api.session.crypto.model.CryptoDeviceInfo +import org.matrix.android.sdk.api.session.crypto.model.MXUsersDevicesMap import org.matrix.android.sdk.internal.crypto.model.CryptoInfoMapper -import org.matrix.android.sdk.internal.crypto.model.MXUsersDevicesMap import org.matrix.android.sdk.internal.crypto.store.IMXCryptoStore import org.matrix.android.sdk.internal.crypto.tasks.DownloadKeysForUsersTask import org.matrix.android.sdk.internal.session.SessionScope 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 00efd3d6a8..1c8bce7377 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 @@ -21,15 +21,17 @@ import kotlinx.coroutines.launch import kotlinx.coroutines.withContext import org.matrix.android.sdk.api.MatrixCallback import org.matrix.android.sdk.api.MatrixCoroutineDispatchers +import org.matrix.android.sdk.api.crypto.MXCRYPTO_ALGORITHM_OLM import org.matrix.android.sdk.api.logger.LoggerTag import org.matrix.android.sdk.api.session.crypto.MXCryptoError +import org.matrix.android.sdk.api.session.crypto.model.MXEventDecryptionResult +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.toModel import org.matrix.android.sdk.internal.crypto.actions.EnsureOlmSessionsForDevicesAction import org.matrix.android.sdk.internal.crypto.actions.MessageEncrypter -import org.matrix.android.sdk.internal.crypto.model.MXUsersDevicesMap -import org.matrix.android.sdk.internal.crypto.model.event.OlmEventContent import org.matrix.android.sdk.internal.crypto.store.IMXCryptoStore import org.matrix.android.sdk.internal.crypto.tasks.SendToDeviceTask import org.matrix.android.sdk.internal.extensions.foldToCallback diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/NewSessionListener.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/GossipRequestType.kt similarity index 86% rename from matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/NewSessionListener.kt rename to matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/GossipRequestType.kt index 301729680c..266c1a2744 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/NewSessionListener.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/GossipRequestType.kt @@ -13,8 +13,10 @@ * See the License for the specific language governing permissions and * limitations under the License. */ + package org.matrix.android.sdk.internal.crypto -interface NewSessionListener { - fun onNewSession(roomId: String?, senderKey: String, sessionId: String) +internal enum class GossipRequestType { + KEY, + SECRET } 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 34bef61c98..28ddf291b2 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/InboundGroupSessionStore.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/InboundGroupSessionStore.kt @@ -30,7 +30,7 @@ import java.util.Timer import java.util.TimerTask import javax.inject.Inject -data class InboundGroupSessionHolder( +internal data class InboundGroupSessionHolder( val wrapper: OlmInboundGroupSessionWrapper2, val mutex: Mutex = Mutex() ) @@ -75,15 +75,15 @@ internal class InboundGroupSessionStore @Inject constructor( @Synchronized fun getInboundGroupSession(sessionId: String, senderKey: String): InboundGroupSessionHolder? { - val known = sessionCache[CacheKey(sessionId, senderKey)] - Timber.tag(loggerTag.value).v("## Inbound: getInboundGroupSession $sessionId in cache ${known != null}") - return known - ?: store.getInboundGroupSession(sessionId, senderKey)?.also { - Timber.tag(loggerTag.value).v("## Inbound: getInboundGroupSession cache populate ${it.roomId}") - sessionCache.put(CacheKey(sessionId, senderKey), InboundGroupSessionHolder(it)) - }?.let { - InboundGroupSessionHolder(it) - } + val known = sessionCache[CacheKey(sessionId, senderKey)] + Timber.tag(loggerTag.value).v("## Inbound: getInboundGroupSession $sessionId in cache ${known != null}") + return known + ?: store.getInboundGroupSession(sessionId, senderKey)?.also { + Timber.tag(loggerTag.value).v("## Inbound: getInboundGroupSession cache populate ${it.roomId}") + sessionCache.put(CacheKey(sessionId, senderKey), InboundGroupSessionHolder(it)) + }?.let { + InboundGroupSessionHolder(it) + } } @Synchronized diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/IncomingGossipingRequestManager.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/IncomingGossipingRequestManager.kt index 220f25ec80..b907b57f82 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/IncomingGossipingRequestManager.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/IncomingGossipingRequestManager.kt @@ -20,21 +20,26 @@ import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.launch import org.matrix.android.sdk.api.MatrixCoroutineDispatchers import org.matrix.android.sdk.api.auth.data.Credentials +import org.matrix.android.sdk.api.crypto.MXCRYPTO_ALGORITHM_MEGOLM import org.matrix.android.sdk.api.crypto.MXCryptoConfig import org.matrix.android.sdk.api.session.crypto.crosssigning.KEYBACKUP_SECRET_SSSS_NAME import org.matrix.android.sdk.api.session.crypto.crosssigning.MASTER_KEY_SSSS_NAME import org.matrix.android.sdk.api.session.crypto.crosssigning.SELF_SIGNING_KEY_SSSS_NAME import org.matrix.android.sdk.api.session.crypto.crosssigning.USER_SIGNING_KEY_SSSS_NAME +import org.matrix.android.sdk.api.session.crypto.keysbackup.extractCurveKeyFromRecoveryKey import org.matrix.android.sdk.api.session.crypto.keyshare.GossipingRequestListener +import org.matrix.android.sdk.api.session.crypto.model.GossipingRequestState +import org.matrix.android.sdk.api.session.crypto.model.GossipingToDeviceObject +import org.matrix.android.sdk.api.session.crypto.model.IncomingRequestCancellation +import org.matrix.android.sdk.api.session.crypto.model.IncomingRoomKeyRequest +import org.matrix.android.sdk.api.session.crypto.model.IncomingSecretShareRequest +import org.matrix.android.sdk.api.session.crypto.model.RoomKeyRequestBody import org.matrix.android.sdk.api.session.events.model.Event import org.matrix.android.sdk.api.session.events.model.EventType import org.matrix.android.sdk.api.session.events.model.toModel +import org.matrix.android.sdk.api.util.toBase64NoPadding import org.matrix.android.sdk.internal.crypto.algorithms.IMXGroupEncryption -import org.matrix.android.sdk.internal.crypto.crosssigning.toBase64NoPadding -import org.matrix.android.sdk.internal.crypto.keysbackup.util.extractCurveKeyFromRecoveryKey import org.matrix.android.sdk.internal.crypto.model.rest.GossipingDefaultContent -import org.matrix.android.sdk.internal.crypto.model.rest.GossipingToDeviceObject -import org.matrix.android.sdk.internal.crypto.model.rest.RoomKeyRequestBody import org.matrix.android.sdk.internal.crypto.store.IMXCryptoStore import org.matrix.android.sdk.internal.crypto.tasks.createUniqueTxnId import org.matrix.android.sdk.internal.di.SessionId @@ -111,7 +116,7 @@ internal class IncomingGossipingRequestManager @Inject constructor( Timber.i("## CRYPTO | GOSSIP onGossipingRequestEvent received type ${event.type} from user:${event.senderId}, content:$roomKeyShare") // val ageLocalTs = event.unsignedData?.age?.let { System.currentTimeMillis() - it } when (roomKeyShare?.action) { - GossipingToDeviceObject.ACTION_SHARE_REQUEST -> { + GossipingToDeviceObject.ACTION_SHARE_REQUEST -> { if (event.getClearType() == EventType.REQUEST_SECRET) { IncomingSecretShareRequest.fromEvent(event)?.let { if (event.senderId == credentials.userId && it.deviceId == credentials.deviceId) { @@ -341,7 +346,7 @@ internal class IncomingGossipingRequestManager @Inject constructor( val isDeviceLocallyVerified = cryptoStore.getUserDevice(userId, deviceId)?.trustLevel?.isLocallyVerified() when (secretName) { - MASTER_KEY_SSSS_NAME -> cryptoStore.getCrossSigningPrivateKeys()?.master + MASTER_KEY_SSSS_NAME -> cryptoStore.getCrossSigningPrivateKeys()?.master SELF_SIGNING_KEY_SSSS_NAME -> cryptoStore.getCrossSigningPrivateKeys()?.selfSigned USER_SIGNING_KEY_SSSS_NAME -> cryptoStore.getCrossSigningPrivateKeys()?.user KEYBACKUP_SECRET_SSSS_NAME -> cryptoStore.getKeyBackupRecoveryKeyInfo()?.recoveryKey diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/IncomingShareRequestCommon.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/IncomingShareRequestCommon.kt index 86e9610148..97c369db3e 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/IncomingShareRequestCommon.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/IncomingShareRequestCommon.kt @@ -16,7 +16,7 @@ package org.matrix.android.sdk.internal.crypto -interface IncomingShareRequestCommon { +internal interface IncomingShareRequestCommon { /** * The user id */ 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 07881c7d79..5a5ee9e696 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 @@ -16,6 +16,9 @@ package org.matrix.android.sdk.internal.crypto +import org.matrix.android.sdk.api.crypto.MXCRYPTO_ALGORITHM_MEGOLM +import org.matrix.android.sdk.api.crypto.MXCRYPTO_ALGORITHM_OLM + // TODO Update comment internal object MXCryptoAlgorithms { diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/MXMegolmExportEncryption.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/MXMegolmExportEncryption.kt index e5ffa0ed7d..f8235bf344 100755 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/MXMegolmExportEncryption.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/MXMegolmExportEncryption.kt @@ -33,7 +33,7 @@ import kotlin.math.min /** * Utility class to import/export the crypto data */ -object MXMegolmExportEncryption { +internal object MXMegolmExportEncryption { private const val HEADER_LINE = "-----BEGIN MEGOLM SESSION DATA-----" private const val TRAILER_LINE = "-----END MEGOLM SESSION DATA-----" 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 501fb42db2..4947761f05 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 @@ -22,11 +22,11 @@ import kotlinx.coroutines.sync.withLock import org.matrix.android.sdk.api.extensions.tryOrNull import org.matrix.android.sdk.api.logger.LoggerTag import org.matrix.android.sdk.api.session.crypto.MXCryptoError +import org.matrix.android.sdk.api.session.crypto.model.OlmDecryptionResult import org.matrix.android.sdk.api.util.JSON_DICT_PARAMETERIZED_TYPE import org.matrix.android.sdk.api.util.JsonDict import org.matrix.android.sdk.internal.crypto.algorithms.megolm.MXOutboundSessionInfo import org.matrix.android.sdk.internal.crypto.algorithms.megolm.SharedWithHelper -import org.matrix.android.sdk.internal.crypto.algorithms.olm.OlmDecryptionResult import org.matrix.android.sdk.internal.crypto.model.OlmInboundGroupSessionWrapper2 import org.matrix.android.sdk.internal.crypto.model.OlmSessionWrapper import org.matrix.android.sdk.internal.crypto.store.IMXCryptoStore diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/MegolmSessionData.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/MegolmSessionData.kt index caff2d76f1..f6bc9a9148 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/MegolmSessionData.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/MegolmSessionData.kt @@ -23,7 +23,7 @@ import com.squareup.moshi.JsonClass * The type of object we use for importing and exporting megolm session data. */ @JsonClass(generateAdapter = true) -data class MegolmSessionData( +internal data class MegolmSessionData( /** * The algorithm used. */ diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/MyDeviceInfoHolder.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/MyDeviceInfoHolder.kt index 70846515a7..9798d21576 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/MyDeviceInfoHolder.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/MyDeviceInfoHolder.kt @@ -17,8 +17,8 @@ package org.matrix.android.sdk.internal.crypto import org.matrix.android.sdk.api.auth.data.Credentials -import org.matrix.android.sdk.internal.crypto.crosssigning.DeviceTrustLevel -import org.matrix.android.sdk.internal.crypto.model.CryptoDeviceInfo +import org.matrix.android.sdk.api.session.crypto.crosssigning.DeviceTrustLevel +import org.matrix.android.sdk.api.session.crypto.model.CryptoDeviceInfo import org.matrix.android.sdk.internal.crypto.store.IMXCryptoStore import org.matrix.android.sdk.internal.session.SessionScope import javax.inject.Inject diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/OneTimeKeysUploader.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/OneTimeKeysUploader.kt index 4aebe091c4..792c9a25dc 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/OneTimeKeysUploader.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/OneTimeKeysUploader.kt @@ -31,7 +31,7 @@ import kotlin.math.min // The spec recommend a 5mn delay, but due to federation // or server downtime we give it a bit more time (1 hour) -const val FALLBACK_KEY_FORGET_DELAY = 60 * 60_000L +private const val FALLBACK_KEY_FORGET_DELAY = 60 * 60_000L @SessionScope internal class OneTimeKeysUploader @Inject constructor( diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/OutgoingGossipingRequest.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/OutgoingGossipingRequest.kt index 8e13daec94..2438e01102 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/OutgoingGossipingRequest.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/OutgoingGossipingRequest.kt @@ -16,10 +16,12 @@ package org.matrix.android.sdk.internal.crypto -interface OutgoingGossipingRequest { - var recipients: Map> - var requestId: String - var state: OutgoingGossipingRequestState +import org.matrix.android.sdk.api.session.crypto.model.OutgoingGossipingRequestState + +internal interface OutgoingGossipingRequest { + val recipients: Map> + val requestId: String + val state: OutgoingGossipingRequestState // transaction id for the cancellation, if any // var cancellationTxnId: String? } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/OutgoingGossipingRequestManager.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/OutgoingGossipingRequestManager.kt index fd60e43260..e6f6ac5053 100755 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/OutgoingGossipingRequestManager.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/OutgoingGossipingRequestManager.kt @@ -20,7 +20,9 @@ import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.delay import kotlinx.coroutines.launch import org.matrix.android.sdk.api.MatrixCoroutineDispatchers -import org.matrix.android.sdk.internal.crypto.model.rest.RoomKeyRequestBody +import org.matrix.android.sdk.api.session.crypto.model.OutgoingGossipingRequestState +import org.matrix.android.sdk.api.session.crypto.model.OutgoingRoomKeyRequest +import org.matrix.android.sdk.api.session.crypto.model.RoomKeyRequestBody import org.matrix.android.sdk.internal.crypto.store.IMXCryptoStore import org.matrix.android.sdk.internal.crypto.tasks.createUniqueTxnId import org.matrix.android.sdk.internal.crypto.util.RequestIdHelper diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/OutgoingSecretRequest.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/OutgoingSecretRequest.kt index def7a1567a..2ba2f5c817 100755 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/OutgoingSecretRequest.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/OutgoingSecretRequest.kt @@ -17,12 +17,13 @@ package org.matrix.android.sdk.internal.crypto import com.squareup.moshi.JsonClass +import org.matrix.android.sdk.api.session.crypto.model.OutgoingGossipingRequestState /** * Represents an outgoing room key request */ @JsonClass(generateAdapter = true) -class OutgoingSecretRequest( +internal class OutgoingSecretRequest( // Secret Name val secretName: String?, // list of recipients for the request 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 89fb43ef2e..dab806a565 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 @@ -16,6 +16,8 @@ package org.matrix.android.sdk.internal.crypto +import org.matrix.android.sdk.api.crypto.MXCRYPTO_ALGORITHM_MEGOLM +import org.matrix.android.sdk.api.session.crypto.NewSessionListener import org.matrix.android.sdk.internal.crypto.algorithms.IMXDecrypting import org.matrix.android.sdk.internal.crypto.algorithms.megolm.MXMegolmDecryptionFactory import org.matrix.android.sdk.internal.crypto.algorithms.olm.MXOlmDecryptionFactory @@ -74,7 +76,7 @@ internal class RoomDecryptorProvider @Inject constructor( this.newSessionListener = object : NewSessionListener { override fun onNewSession(roomId: String?, senderKey: String, sessionId: String) { // PR reviewer: the parameter has been renamed so is now in conflict with the parameter of getOrCreateRoomDecryptor - newSessionListeners.forEach { + newSessionListeners.toList().forEach { try { it.onNewSession(roomId, senderKey, sessionId) } catch (e: Throwable) { diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/SendGossipRequestWorker.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/SendGossipRequestWorker.kt index 3129ccae3b..69b405aedc 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/SendGossipRequestWorker.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/SendGossipRequestWorker.kt @@ -21,14 +21,16 @@ import androidx.work.WorkerParameters import com.squareup.moshi.JsonClass import org.matrix.android.sdk.api.auth.data.Credentials import org.matrix.android.sdk.api.failure.shouldBeRetried +import org.matrix.android.sdk.api.session.crypto.model.GossipingToDeviceObject +import org.matrix.android.sdk.api.session.crypto.model.MXUsersDevicesMap +import org.matrix.android.sdk.api.session.crypto.model.OutgoingGossipingRequestState +import org.matrix.android.sdk.api.session.crypto.model.OutgoingRoomKeyRequest +import org.matrix.android.sdk.api.session.crypto.model.RoomKeyShareRequest +import org.matrix.android.sdk.api.session.crypto.model.SecretShareRequest import org.matrix.android.sdk.api.session.events.model.Event import org.matrix.android.sdk.api.session.events.model.EventType import org.matrix.android.sdk.api.session.events.model.toContent import org.matrix.android.sdk.internal.SessionManager -import org.matrix.android.sdk.internal.crypto.model.MXUsersDevicesMap -import org.matrix.android.sdk.internal.crypto.model.rest.GossipingToDeviceObject -import org.matrix.android.sdk.internal.crypto.model.rest.RoomKeyShareRequest -import org.matrix.android.sdk.internal.crypto.model.rest.SecretShareRequest import org.matrix.android.sdk.internal.crypto.store.IMXCryptoStore import org.matrix.android.sdk.internal.crypto.tasks.SendToDeviceTask import org.matrix.android.sdk.internal.crypto.tasks.createUniqueTxnId @@ -39,7 +41,7 @@ import timber.log.Timber import javax.inject.Inject internal class SendGossipRequestWorker(context: Context, params: WorkerParameters, sessionManager: SessionManager) : - SessionSafeCoroutineWorker(context, params, sessionManager, Params::class.java) { + SessionSafeCoroutineWorker(context, params, sessionManager, Params::class.java) { @JsonClass(generateAdapter = true) internal data class Params( diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/SendGossipWorker.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/SendGossipWorker.kt index ff206a3c96..fd472fe73b 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/SendGossipWorker.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/SendGossipWorker.kt @@ -21,14 +21,15 @@ import androidx.work.WorkerParameters import com.squareup.moshi.JsonClass import org.matrix.android.sdk.api.auth.data.Credentials import org.matrix.android.sdk.api.failure.shouldBeRetried +import org.matrix.android.sdk.api.session.crypto.model.GossipingRequestState +import org.matrix.android.sdk.api.session.crypto.model.MXUsersDevicesMap import org.matrix.android.sdk.api.session.events.model.Event import org.matrix.android.sdk.api.session.events.model.EventType +import org.matrix.android.sdk.api.session.events.model.content.SecretSendEventContent import org.matrix.android.sdk.api.session.events.model.toContent import org.matrix.android.sdk.internal.SessionManager import org.matrix.android.sdk.internal.crypto.actions.EnsureOlmSessionsForDevicesAction import org.matrix.android.sdk.internal.crypto.actions.MessageEncrypter -import org.matrix.android.sdk.internal.crypto.model.MXUsersDevicesMap -import org.matrix.android.sdk.internal.crypto.model.event.SecretSendEventContent import org.matrix.android.sdk.internal.crypto.store.IMXCryptoStore import org.matrix.android.sdk.internal.crypto.tasks.SendToDeviceTask import org.matrix.android.sdk.internal.crypto.tasks.createUniqueTxnId @@ -38,8 +39,11 @@ import org.matrix.android.sdk.internal.worker.SessionWorkerParams import timber.log.Timber import javax.inject.Inject -internal class SendGossipWorker(context: Context, params: WorkerParameters, sessionManager: SessionManager) : - SessionSafeCoroutineWorker(context, params, sessionManager, Params::class.java) { +internal class SendGossipWorker( + context: Context, + params: WorkerParameters, + sessionManager: SessionManager +) : SessionSafeCoroutineWorker(context, params, sessionManager, Params::class.java) { @JsonClass(generateAdapter = true) internal data class Params( diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/actions/EnsureOlmSessionsForDevicesAction.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/actions/EnsureOlmSessionsForDevicesAction.kt index 87c176612d..fffc2b4d4b 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 @@ -21,11 +21,11 @@ import kotlinx.coroutines.sync.withLock import kotlinx.coroutines.withContext import org.matrix.android.sdk.api.MatrixCoroutineDispatchers import org.matrix.android.sdk.api.logger.LoggerTag +import org.matrix.android.sdk.api.session.crypto.model.CryptoDeviceInfo +import org.matrix.android.sdk.api.session.crypto.model.MXUsersDevicesMap import org.matrix.android.sdk.internal.crypto.MXOlmDevice -import org.matrix.android.sdk.internal.crypto.model.CryptoDeviceInfo import org.matrix.android.sdk.internal.crypto.model.MXKey import org.matrix.android.sdk.internal.crypto.model.MXOlmSessionResult -import org.matrix.android.sdk.internal.crypto.model.MXUsersDevicesMap import org.matrix.android.sdk.internal.crypto.tasks.ClaimOneTimeKeysForUsersDeviceTask import org.matrix.android.sdk.internal.session.SessionScope import timber.log.Timber 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 a3cfbd91f0..fc211537a6 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 @@ -16,9 +16,9 @@ package org.matrix.android.sdk.internal.crypto.actions +import org.matrix.android.sdk.api.session.crypto.model.MXUsersDevicesMap import org.matrix.android.sdk.internal.crypto.MXOlmDevice import org.matrix.android.sdk.internal.crypto.model.MXOlmSessionResult -import org.matrix.android.sdk.internal.crypto.model.MXUsersDevicesMap import org.matrix.android.sdk.internal.crypto.store.IMXCryptoStore import timber.log.Timber import javax.inject.Inject 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 0d78f68e5c..f9bcdf2c68 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 @@ -18,12 +18,13 @@ package org.matrix.android.sdk.internal.crypto.actions import androidx.annotation.WorkerThread import org.matrix.android.sdk.api.listeners.ProgressListener +import org.matrix.android.sdk.api.session.crypto.model.ImportRoomKeysResult +import org.matrix.android.sdk.api.session.crypto.model.RoomKeyRequestBody import org.matrix.android.sdk.internal.crypto.MXOlmDevice import org.matrix.android.sdk.internal.crypto.MegolmSessionData import org.matrix.android.sdk.internal.crypto.OutgoingGossipingRequestManager import org.matrix.android.sdk.internal.crypto.RoomDecryptorProvider -import org.matrix.android.sdk.internal.crypto.model.ImportRoomKeysResult -import org.matrix.android.sdk.internal.crypto.model.rest.RoomKeyRequestBody +import org.matrix.android.sdk.internal.crypto.algorithms.megolm.MXMegolmDecryption import org.matrix.android.sdk.internal.crypto.store.IMXCryptoStore import timber.log.Timber import javax.inject.Inject @@ -76,7 +77,11 @@ internal class MegolmSessionDataImporter @Inject constructor(private val olmDevi outgoingGossipingRequestManager.cancelRoomKeyRequest(roomKeyRequestBody) // Have another go at decrypting events sent with this session - decrypting.onNewSession(megolmSessionData.senderKey!!, sessionId!!) + when (decrypting) { + is MXMegolmDecryption -> { + decrypting.onNewSession(megolmSessionData.roomId, megolmSessionData.senderKey!!, sessionId!!) + } + } } catch (e: Exception) { Timber.e(e, "## importRoomKeys() : onNewSession failed") } 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 4e158602c8..9bbbab4992 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 @@ -16,11 +16,11 @@ package org.matrix.android.sdk.internal.crypto.actions +import org.matrix.android.sdk.api.crypto.MXCRYPTO_ALGORITHM_OLM import org.matrix.android.sdk.api.logger.LoggerTag +import org.matrix.android.sdk.api.session.crypto.model.CryptoDeviceInfo import org.matrix.android.sdk.api.session.events.model.Content -import org.matrix.android.sdk.internal.crypto.MXCRYPTO_ALGORITHM_OLM import org.matrix.android.sdk.internal.crypto.MXOlmDevice -import org.matrix.android.sdk.internal.crypto.model.CryptoDeviceInfo import org.matrix.android.sdk.internal.crypto.model.rest.EncryptedMessage import org.matrix.android.sdk.internal.di.DeviceId import org.matrix.android.sdk.internal.di.UserId 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 40eddc82bd..60181138fb 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 @@ -16,7 +16,7 @@ package org.matrix.android.sdk.internal.crypto.actions -import org.matrix.android.sdk.internal.crypto.crosssigning.DeviceTrustLevel +import org.matrix.android.sdk.api.session.crypto.crosssigning.DeviceTrustLevel import org.matrix.android.sdk.internal.crypto.keysbackup.DefaultKeysBackupService import org.matrix.android.sdk.internal.crypto.store.IMXCryptoStore import org.matrix.android.sdk.internal.di.UserId diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/algorithms/IMXDecrypting.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/algorithms/IMXDecrypting.kt index b6c1d99aa5..2ea4e1dd29 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/algorithms/IMXDecrypting.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/algorithms/IMXDecrypting.kt @@ -17,10 +17,10 @@ package org.matrix.android.sdk.internal.crypto.algorithms import org.matrix.android.sdk.api.session.crypto.MXCryptoError +import org.matrix.android.sdk.api.session.crypto.model.IncomingRoomKeyRequest +import org.matrix.android.sdk.api.session.crypto.model.IncomingSecretShareRequest +import org.matrix.android.sdk.api.session.crypto.model.MXEventDecryptionResult import org.matrix.android.sdk.api.session.events.model.Event -import org.matrix.android.sdk.internal.crypto.IncomingRoomKeyRequest -import org.matrix.android.sdk.internal.crypto.IncomingSecretShareRequest -import org.matrix.android.sdk.internal.crypto.MXEventDecryptionResult import org.matrix.android.sdk.internal.crypto.keysbackup.DefaultKeysBackupService /** @@ -45,14 +45,6 @@ internal interface IMXDecrypting { */ fun onRoomKeyEvent(event: Event, defaultKeysBackupService: DefaultKeysBackupService) {} - /** - * Check if the some messages can be decrypted with a new session - * - * @param senderKey the session sender key - * @param sessionId the session id - */ - fun onNewSession(senderKey: String, sessionId: String) {} - /** * Determine if we have the keys necessary to respond to a room key request * diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/algorithms/IMXWithHeldExtension.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/algorithms/IMXWithHeldExtension.kt index 91f10adf4c..585bcdbbde 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/algorithms/IMXWithHeldExtension.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/algorithms/IMXWithHeldExtension.kt @@ -16,7 +16,7 @@ package org.matrix.android.sdk.internal.crypto.algorithms -import org.matrix.android.sdk.internal.crypto.model.event.RoomKeyWithHeldContent +import org.matrix.android.sdk.api.session.events.model.content.RoomKeyWithHeldContent internal interface IMXWithHeldExtension { fun onRoomKeyWithHeldEvent(withHeldInfo: RoomKeyWithHeldContent) 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 e94daa0e76..4c407c9eb9 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 @@ -23,26 +23,26 @@ import kotlinx.coroutines.sync.withLock import org.matrix.android.sdk.api.MatrixCoroutineDispatchers import org.matrix.android.sdk.api.logger.LoggerTag import org.matrix.android.sdk.api.session.crypto.MXCryptoError +import org.matrix.android.sdk.api.session.crypto.NewSessionListener +import org.matrix.android.sdk.api.session.crypto.model.ForwardedRoomKeyContent +import org.matrix.android.sdk.api.session.crypto.model.IncomingRoomKeyRequest +import org.matrix.android.sdk.api.session.crypto.model.MXEventDecryptionResult +import org.matrix.android.sdk.api.session.crypto.model.MXUsersDevicesMap +import org.matrix.android.sdk.api.session.crypto.model.RoomKeyRequestBody import org.matrix.android.sdk.api.session.events.model.Event import org.matrix.android.sdk.api.session.events.model.EventType +import org.matrix.android.sdk.api.session.events.model.content.EncryptedEventContent +import org.matrix.android.sdk.api.session.events.model.content.RoomKeyContent +import org.matrix.android.sdk.api.session.events.model.content.RoomKeyWithHeldContent import org.matrix.android.sdk.api.session.events.model.toModel import org.matrix.android.sdk.internal.crypto.DeviceListManager -import org.matrix.android.sdk.internal.crypto.IncomingRoomKeyRequest -import org.matrix.android.sdk.internal.crypto.MXEventDecryptionResult import org.matrix.android.sdk.internal.crypto.MXOlmDevice -import org.matrix.android.sdk.internal.crypto.NewSessionListener import org.matrix.android.sdk.internal.crypto.OutgoingGossipingRequestManager import org.matrix.android.sdk.internal.crypto.actions.EnsureOlmSessionsForDevicesAction import org.matrix.android.sdk.internal.crypto.actions.MessageEncrypter import org.matrix.android.sdk.internal.crypto.algorithms.IMXDecrypting import org.matrix.android.sdk.internal.crypto.algorithms.IMXWithHeldExtension import org.matrix.android.sdk.internal.crypto.keysbackup.DefaultKeysBackupService -import org.matrix.android.sdk.internal.crypto.model.MXUsersDevicesMap -import org.matrix.android.sdk.internal.crypto.model.event.EncryptedEventContent -import org.matrix.android.sdk.internal.crypto.model.event.RoomKeyContent -import org.matrix.android.sdk.internal.crypto.model.event.RoomKeyWithHeldContent -import org.matrix.android.sdk.internal.crypto.model.rest.ForwardedRoomKeyContent -import org.matrix.android.sdk.internal.crypto.model.rest.RoomKeyRequestBody import org.matrix.android.sdk.internal.crypto.store.IMXCryptoStore import org.matrix.android.sdk.internal.crypto.tasks.SendToDeviceTask import org.matrix.android.sdk.internal.session.StreamEventsManager @@ -318,19 +318,20 @@ internal class MXMegolmDecryption(private val userId: String, outgoingGossipingRequestManager.cancelRoomKeyRequest(content) - onNewSession(senderKey, roomKeyContent.sessionId) + onNewSession(roomKeyContent.roomId, senderKey, roomKeyContent.sessionId) } } /** * Check if the some messages can be decrypted with a new session * + * @param roomId the room id where the new Megolm session has been created for, may be null when importing from external sessions * @param senderKey the session sender key * @param sessionId the session id */ - override fun onNewSession(senderKey: String, sessionId: String) { + fun onNewSession(roomId: String?, senderKey: String, sessionId: String) { Timber.tag(loggerTag.value).v("ON NEW SESSION $sessionId - $senderKey") - newSessionListener?.onNewSession(null, senderKey, sessionId) + newSessionListener?.onNewSession(roomId, senderKey, sessionId) } override fun hasKeysForKeyRequest(request: IncomingRoomKeyRequest): Boolean { 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 cf9733dc2d..f052194230 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 @@ -21,24 +21,24 @@ import kotlinx.coroutines.launch import kotlinx.coroutines.sync.withLock import kotlinx.coroutines.withContext import org.matrix.android.sdk.api.MatrixCoroutineDispatchers +import org.matrix.android.sdk.api.crypto.MXCRYPTO_ALGORITHM_MEGOLM import org.matrix.android.sdk.api.logger.LoggerTag import org.matrix.android.sdk.api.session.crypto.MXCryptoError +import org.matrix.android.sdk.api.session.crypto.model.CryptoDeviceInfo +import org.matrix.android.sdk.api.session.crypto.model.MXUsersDevicesMap +import org.matrix.android.sdk.api.session.crypto.model.forEach import org.matrix.android.sdk.api.session.events.model.Content import org.matrix.android.sdk.api.session.events.model.Event import org.matrix.android.sdk.api.session.events.model.EventType +import org.matrix.android.sdk.api.session.events.model.content.RoomKeyWithHeldContent +import org.matrix.android.sdk.api.session.events.model.content.WithHeldCode import org.matrix.android.sdk.internal.crypto.DeviceListManager -import org.matrix.android.sdk.internal.crypto.MXCRYPTO_ALGORITHM_MEGOLM import org.matrix.android.sdk.internal.crypto.MXOlmDevice import org.matrix.android.sdk.internal.crypto.actions.EnsureOlmSessionsForDevicesAction import org.matrix.android.sdk.internal.crypto.actions.MessageEncrypter import org.matrix.android.sdk.internal.crypto.algorithms.IMXEncrypting import org.matrix.android.sdk.internal.crypto.algorithms.IMXGroupEncryption import org.matrix.android.sdk.internal.crypto.keysbackup.DefaultKeysBackupService -import org.matrix.android.sdk.internal.crypto.model.CryptoDeviceInfo -import org.matrix.android.sdk.internal.crypto.model.MXUsersDevicesMap -import org.matrix.android.sdk.internal.crypto.model.event.RoomKeyWithHeldContent -import org.matrix.android.sdk.internal.crypto.model.event.WithHeldCode -import org.matrix.android.sdk.internal.crypto.model.forEach import org.matrix.android.sdk.internal.crypto.model.toDebugCount import org.matrix.android.sdk.internal.crypto.model.toDebugString import org.matrix.android.sdk.internal.crypto.repository.WarnOnUnknownDeviceRepository diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/algorithms/megolm/MXOutboundSessionInfo.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/algorithms/megolm/MXOutboundSessionInfo.kt index b70e6c1f80..091abd4974 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/algorithms/megolm/MXOutboundSessionInfo.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/algorithms/megolm/MXOutboundSessionInfo.kt @@ -16,8 +16,8 @@ package org.matrix.android.sdk.internal.crypto.algorithms.megolm -import org.matrix.android.sdk.internal.crypto.model.CryptoDeviceInfo -import org.matrix.android.sdk.internal.crypto.model.MXUsersDevicesMap +import org.matrix.android.sdk.api.session.crypto.model.CryptoDeviceInfo +import org.matrix.android.sdk.api.session.crypto.model.MXUsersDevicesMap import timber.log.Timber internal class MXOutboundSessionInfo( 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 a64e5af0d3..59d78c3e05 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 @@ -16,8 +16,8 @@ package org.matrix.android.sdk.internal.crypto.algorithms.megolm -import org.matrix.android.sdk.internal.crypto.model.CryptoDeviceInfo -import org.matrix.android.sdk.internal.crypto.model.MXUsersDevicesMap +import org.matrix.android.sdk.api.session.crypto.model.CryptoDeviceInfo +import org.matrix.android.sdk.api.session.crypto.model.MXUsersDevicesMap import org.matrix.android.sdk.internal.crypto.store.IMXCryptoStore internal class SharedWithHelper( 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 afa249801d..0db8700852 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 @@ -19,26 +19,27 @@ package org.matrix.android.sdk.internal.crypto.algorithms.olm import kotlinx.coroutines.sync.withLock import org.matrix.android.sdk.api.logger.LoggerTag import org.matrix.android.sdk.api.session.crypto.MXCryptoError +import org.matrix.android.sdk.api.session.crypto.model.MXEventDecryptionResult import org.matrix.android.sdk.api.session.events.model.Event +import org.matrix.android.sdk.api.session.events.model.content.OlmEventContent +import org.matrix.android.sdk.api.session.events.model.content.OlmPayloadContent import org.matrix.android.sdk.api.session.events.model.toModel import org.matrix.android.sdk.api.util.JSON_DICT_PARAMETERIZED_TYPE import org.matrix.android.sdk.api.util.JsonDict -import org.matrix.android.sdk.internal.crypto.MXEventDecryptionResult import org.matrix.android.sdk.internal.crypto.MXOlmDevice import org.matrix.android.sdk.internal.crypto.algorithms.IMXDecrypting -import org.matrix.android.sdk.internal.crypto.model.event.OlmEventContent -import org.matrix.android.sdk.internal.crypto.model.event.OlmPayloadContent import org.matrix.android.sdk.internal.di.MoshiProvider import org.matrix.android.sdk.internal.util.convertFromUTF8 import timber.log.Timber private val loggerTag = LoggerTag("MXOlmDecryption", LoggerTag.CRYPTO) + internal class MXOlmDecryption( // The olm device interface private val olmDevice: MXOlmDevice, // the matrix userId private val userId: String) : - IMXDecrypting { + IMXDecrypting { @Throws(MXCryptoError::class) override suspend fun decryptEvent(event: Event, timeline: String): MXEventDecryptionResult { 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 63f2533ac3..c842c54041 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/algorithms/olm/MXOlmEncryption.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/algorithms/olm/MXOlmEncryption.kt @@ -16,6 +16,7 @@ package org.matrix.android.sdk.internal.crypto.algorithms.olm +import org.matrix.android.sdk.api.session.crypto.model.CryptoDeviceInfo import org.matrix.android.sdk.api.session.events.model.Content import org.matrix.android.sdk.api.session.events.model.toContent import org.matrix.android.sdk.internal.crypto.DeviceListManager @@ -23,7 +24,6 @@ import org.matrix.android.sdk.internal.crypto.MXOlmDevice import org.matrix.android.sdk.internal.crypto.actions.EnsureOlmSessionsForUsersAction import org.matrix.android.sdk.internal.crypto.actions.MessageEncrypter import org.matrix.android.sdk.internal.crypto.algorithms.IMXEncrypting -import org.matrix.android.sdk.internal.crypto.model.CryptoDeviceInfo import org.matrix.android.sdk.internal.crypto.store.IMXCryptoStore internal class MXOlmEncryption( @@ -33,7 +33,7 @@ internal class MXOlmEncryption( private val messageEncrypter: MessageEncrypter, private val deviceListManager: DeviceListManager, private val ensureOlmSessionsForUsersAction: EnsureOlmSessionsForUsersAction) : - IMXEncrypting { + IMXEncrypting { override suspend fun encryptEventContent(eventContent: Content, eventType: String, userIds: List): Content { // pick the list of recipients based on the membership list. diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/api/CryptoApi.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/api/CryptoApi.kt index cef86e8b5e..f21f5e05e1 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 @@ -15,9 +15,9 @@ */ package org.matrix.android.sdk.internal.crypto.api +import org.matrix.android.sdk.api.session.crypto.model.DeviceInfo +import org.matrix.android.sdk.api.session.crypto.model.DevicesListResponse import org.matrix.android.sdk.internal.crypto.model.rest.DeleteDeviceParams -import org.matrix.android.sdk.internal.crypto.model.rest.DeviceInfo -import org.matrix.android.sdk.internal.crypto.model.rest.DevicesListResponse import org.matrix.android.sdk.internal.crypto.model.rest.KeyChangesResponse import org.matrix.android.sdk.internal.crypto.model.rest.KeysClaimBody import org.matrix.android.sdk.internal.crypto.model.rest.KeysClaimResponse diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/attachments/EncryptionResult.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/attachments/EncryptionResult.kt index ba5baba60d..80090cf4a8 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/attachments/EncryptionResult.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/attachments/EncryptionResult.kt @@ -16,12 +16,12 @@ package org.matrix.android.sdk.internal.crypto.attachments -import org.matrix.android.sdk.internal.crypto.model.rest.EncryptedFileInfo +import org.matrix.android.sdk.api.session.crypto.model.EncryptedFileInfo /** * Define the result of an encryption file */ internal data class EncryptionResult( - var encryptedFileInfo: EncryptedFileInfo, - var encryptedByteArray: ByteArray + val encryptedFileInfo: EncryptedFileInfo, + val encryptedByteArray: ByteArray ) 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 70730326da..91b6af6fc3 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 @@ -17,8 +17,9 @@ package org.matrix.android.sdk.internal.crypto.attachments import android.util.Base64 -import org.matrix.android.sdk.internal.crypto.model.rest.EncryptedFileInfo -import org.matrix.android.sdk.internal.crypto.model.rest.EncryptedFileKey +import org.matrix.android.sdk.api.session.crypto.attachments.ElementToDecrypt +import org.matrix.android.sdk.api.session.crypto.model.EncryptedFileInfo +import org.matrix.android.sdk.api.session.crypto.model.EncryptedFileKey import org.matrix.android.sdk.internal.util.base64ToBase64Url import org.matrix.android.sdk.internal.util.base64ToUnpaddedBase64 import org.matrix.android.sdk.internal.util.base64UrlToBase64 diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/attachments/MatrixDigestCheckInputStream.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/attachments/MatrixDigestCheckInputStream.kt deleted file mode 100644 index 2cbe0e3702..0000000000 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/attachments/MatrixDigestCheckInputStream.kt +++ /dev/null @@ -1,69 +0,0 @@ -/* - * Copyright 2020 The Matrix.org Foundation C.I.C. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.matrix.android.sdk.internal.crypto.attachments - -import android.util.Base64 -import org.matrix.android.sdk.internal.util.base64ToUnpaddedBase64 -import java.io.FilterInputStream -import java.io.IOException -import java.io.InputStream -import java.security.MessageDigest - -class MatrixDigestCheckInputStream( - inputStream: InputStream?, - private val expectedDigest: String -) : FilterInputStream(inputStream) { - - private val digest = MessageDigest.getInstance("SHA-256") - - @Throws(IOException::class) - override fun read(): Int { - val b = `in`.read() - if (b >= 0) { - digest.update(b.toByte()) - } - - if (b == -1) { - ensureDigest() - } - return b - } - - @Throws(IOException::class) - override fun read( - b: ByteArray, - off: Int, - len: Int): Int { - val n = `in`.read(b, off, len) - if (n > 0) { - digest.update(b, off, n) - } - - if (n == -1) { - ensureDigest() - } - return n - } - - @Throws(IOException::class) - private fun ensureDigest() { - val currentDigestValue = base64ToUnpaddedBase64(Base64.encodeToString(digest.digest(), Base64.DEFAULT)) - if (currentDigestValue != expectedDigest) { - throw IOException("Bad digest") - } - } -} diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/crosssigning/ComputeTrustTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/crosssigning/ComputeTrustTask.kt index b470ab34bb..02ea943284 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/crosssigning/ComputeTrustTask.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/crosssigning/ComputeTrustTask.kt @@ -17,9 +17,9 @@ package org.matrix.android.sdk.internal.crypto.crosssigning import kotlinx.coroutines.withContext import org.matrix.android.sdk.api.MatrixCoroutineDispatchers -import org.matrix.android.sdk.api.crypto.RoomEncryptionTrustLevel import org.matrix.android.sdk.api.extensions.orFalse import org.matrix.android.sdk.api.session.crypto.crosssigning.MXCrossSigningInfo +import org.matrix.android.sdk.api.session.crypto.model.RoomEncryptionTrustLevel import org.matrix.android.sdk.internal.crypto.store.IMXCryptoStore import org.matrix.android.sdk.internal.di.UserId import org.matrix.android.sdk.internal.task.Task diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/crosssigning/DefaultCrossSigningService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/crosssigning/DefaultCrossSigningService.kt index 83de06a668..ba1718688f 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 @@ -26,13 +26,20 @@ import org.matrix.android.sdk.api.MatrixCoroutineDispatchers import org.matrix.android.sdk.api.auth.UserInteractiveAuthInterceptor import org.matrix.android.sdk.api.extensions.orFalse import org.matrix.android.sdk.api.session.crypto.crosssigning.CrossSigningService +import org.matrix.android.sdk.api.session.crypto.crosssigning.DeviceTrustLevel +import org.matrix.android.sdk.api.session.crypto.crosssigning.DeviceTrustResult import org.matrix.android.sdk.api.session.crypto.crosssigning.MXCrossSigningInfo +import org.matrix.android.sdk.api.session.crypto.crosssigning.PrivateKeysInfo +import org.matrix.android.sdk.api.session.crypto.crosssigning.UserTrustResult +import org.matrix.android.sdk.api.session.crypto.crosssigning.isCrossSignedVerified +import org.matrix.android.sdk.api.session.crypto.crosssigning.isLocallyVerified +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.util.Optional +import org.matrix.android.sdk.api.util.fromBase64 import org.matrix.android.sdk.internal.crypto.DeviceListManager -import org.matrix.android.sdk.internal.crypto.model.CryptoDeviceInfo import org.matrix.android.sdk.internal.crypto.model.rest.UploadSignatureQueryBuilder import org.matrix.android.sdk.internal.crypto.store.IMXCryptoStore -import org.matrix.android.sdk.internal.crypto.store.PrivateKeysInfo import org.matrix.android.sdk.internal.crypto.tasks.InitializeCrossSigningTask import org.matrix.android.sdk.internal.crypto.tasks.UploadSignaturesTask import org.matrix.android.sdk.internal.di.SessionId diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/crosssigning/Extensions.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/crosssigning/Extensions.kt index cf2d6aa269..16098e5210 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/crosssigning/Extensions.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/crosssigning/Extensions.kt @@ -16,8 +16,8 @@ package org.matrix.android.sdk.internal.crypto.crosssigning import android.util.Base64 -import org.matrix.android.sdk.internal.crypto.model.CryptoCrossSigningKey -import org.matrix.android.sdk.internal.crypto.model.CryptoDeviceInfo +import org.matrix.android.sdk.api.session.crypto.crosssigning.CryptoCrossSigningKey +import org.matrix.android.sdk.api.session.crypto.model.CryptoDeviceInfo import org.matrix.android.sdk.internal.util.JsonCanonicalizer import timber.log.Timber @@ -29,14 +29,6 @@ internal fun CryptoCrossSigningKey.canonicalSignable(): String { return JsonCanonicalizer.getCanonicalJson(Map::class.java, signalableJSONDictionary()) } -fun ByteArray.toBase64NoPadding(): String { - return Base64.encodeToString(this, Base64.NO_PADDING or Base64.NO_WRAP) -} - -fun String.fromBase64(): ByteArray { - return Base64.decode(this, Base64.DEFAULT) -} - /** * Decode the base 64. Return null in case of bad format. Should be used when parsing received data from external source */ 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 9325355d28..74f0f5745d 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 @@ -22,9 +22,12 @@ import com.squareup.moshi.JsonClass import io.realm.Realm import io.realm.RealmConfiguration import io.realm.kotlin.where -import org.matrix.android.sdk.api.crypto.RoomEncryptionTrustLevel import org.matrix.android.sdk.api.extensions.orFalse import org.matrix.android.sdk.api.session.crypto.crosssigning.MXCrossSigningInfo +import org.matrix.android.sdk.api.session.crypto.crosssigning.UserTrustResult +import org.matrix.android.sdk.api.session.crypto.crosssigning.isCrossSignedVerified +import org.matrix.android.sdk.api.session.crypto.crosssigning.isVerified +import org.matrix.android.sdk.api.session.crypto.model.RoomEncryptionTrustLevel import org.matrix.android.sdk.internal.SessionManager import org.matrix.android.sdk.internal.crypto.store.IMXCryptoStore import org.matrix.android.sdk.internal.crypto.store.db.mapper.CrossSigningKeysMapper @@ -52,7 +55,7 @@ import timber.log.Timber import javax.inject.Inject internal class UpdateTrustWorker(context: Context, params: WorkerParameters, sessionManager: SessionManager) : - SessionSafeCoroutineWorker(context, params, sessionManager, Params::class.java) { + SessionSafeCoroutineWorker(context, params, sessionManager, Params::class.java) { @JsonClass(generateAdapter = true) internal data class Params( diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/keysbackup/DefaultKeysBackupService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/keysbackup/DefaultKeysBackupService.kt index 954c2dbe43..e63a6dc791 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 @@ -28,30 +28,37 @@ import kotlinx.coroutines.withContext import org.matrix.android.sdk.api.MatrixCallback import org.matrix.android.sdk.api.MatrixCoroutineDispatchers import org.matrix.android.sdk.api.auth.data.Credentials +import org.matrix.android.sdk.api.crypto.MXCRYPTO_ALGORITHM_MEGOLM_BACKUP import org.matrix.android.sdk.api.failure.Failure import org.matrix.android.sdk.api.failure.MatrixError import org.matrix.android.sdk.api.listeners.ProgressListener import org.matrix.android.sdk.api.listeners.StepProgressListener +import org.matrix.android.sdk.api.session.crypto.keysbackup.KeysBackupLastVersionResult import org.matrix.android.sdk.api.session.crypto.keysbackup.KeysBackupService import org.matrix.android.sdk.api.session.crypto.keysbackup.KeysBackupState import org.matrix.android.sdk.api.session.crypto.keysbackup.KeysBackupStateListener -import org.matrix.android.sdk.internal.crypto.MXCRYPTO_ALGORITHM_MEGOLM_BACKUP +import org.matrix.android.sdk.api.session.crypto.keysbackup.KeysBackupVersionTrust +import org.matrix.android.sdk.api.session.crypto.keysbackup.KeysBackupVersionTrustSignature +import org.matrix.android.sdk.api.session.crypto.keysbackup.KeysVersion +import org.matrix.android.sdk.api.session.crypto.keysbackup.KeysVersionResult +import org.matrix.android.sdk.api.session.crypto.keysbackup.MegolmBackupAuthData +import org.matrix.android.sdk.api.session.crypto.keysbackup.MegolmBackupCreationInfo +import org.matrix.android.sdk.api.session.crypto.keysbackup.SavedKeyBackupKeyInfo +import org.matrix.android.sdk.api.session.crypto.keysbackup.computeRecoveryKey +import org.matrix.android.sdk.api.session.crypto.keysbackup.extractCurveKeyFromRecoveryKey +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.util.awaitCallback +import org.matrix.android.sdk.api.util.fromBase64 import org.matrix.android.sdk.internal.crypto.MXOlmDevice import org.matrix.android.sdk.internal.crypto.MegolmSessionData import org.matrix.android.sdk.internal.crypto.ObjectSigner import org.matrix.android.sdk.internal.crypto.actions.MegolmSessionDataImporter -import org.matrix.android.sdk.internal.crypto.crosssigning.fromBase64 -import org.matrix.android.sdk.internal.crypto.keysbackup.model.KeysBackupVersionTrust -import org.matrix.android.sdk.internal.crypto.keysbackup.model.KeysBackupVersionTrustSignature -import org.matrix.android.sdk.internal.crypto.keysbackup.model.MegolmBackupAuthData -import org.matrix.android.sdk.internal.crypto.keysbackup.model.MegolmBackupCreationInfo import org.matrix.android.sdk.internal.crypto.keysbackup.model.SignalableMegolmBackupAuthData import org.matrix.android.sdk.internal.crypto.keysbackup.model.rest.BackupKeysResult import org.matrix.android.sdk.internal.crypto.keysbackup.model.rest.CreateKeysBackupVersionBody import org.matrix.android.sdk.internal.crypto.keysbackup.model.rest.KeyBackupData import org.matrix.android.sdk.internal.crypto.keysbackup.model.rest.KeysBackupData -import org.matrix.android.sdk.internal.crypto.keysbackup.model.rest.KeysVersion -import org.matrix.android.sdk.internal.crypto.keysbackup.model.rest.KeysVersionResult import org.matrix.android.sdk.internal.crypto.keysbackup.model.rest.RoomKeysBackupData import org.matrix.android.sdk.internal.crypto.keysbackup.model.rest.UpdateKeysBackupVersionBody import org.matrix.android.sdk.internal.crypto.keysbackup.tasks.CreateKeysBackupVersionTask @@ -68,12 +75,8 @@ import org.matrix.android.sdk.internal.crypto.keysbackup.tasks.StoreRoomSessionD import org.matrix.android.sdk.internal.crypto.keysbackup.tasks.StoreRoomSessionsDataTask import org.matrix.android.sdk.internal.crypto.keysbackup.tasks.StoreSessionsDataTask import org.matrix.android.sdk.internal.crypto.keysbackup.tasks.UpdateKeysBackupVersionTask -import org.matrix.android.sdk.internal.crypto.keysbackup.util.computeRecoveryKey -import org.matrix.android.sdk.internal.crypto.keysbackup.util.extractCurveKeyFromRecoveryKey -import org.matrix.android.sdk.internal.crypto.model.ImportRoomKeysResult import org.matrix.android.sdk.internal.crypto.model.OlmInboundGroupSessionWrapper2 import org.matrix.android.sdk.internal.crypto.store.IMXCryptoStore -import org.matrix.android.sdk.internal.crypto.store.SavedKeyBackupKeyInfo import org.matrix.android.sdk.internal.crypto.store.db.model.KeysBackupDataEntity import org.matrix.android.sdk.internal.di.MoshiProvider import org.matrix.android.sdk.internal.di.UserId @@ -84,7 +87,6 @@ import org.matrix.android.sdk.internal.task.TaskExecutor import org.matrix.android.sdk.internal.task.TaskThread import org.matrix.android.sdk.internal.task.configureWith import org.matrix.android.sdk.internal.util.JsonCanonicalizer -import org.matrix.android.sdk.internal.util.awaitCallback import org.matrix.olm.OlmException import org.matrix.olm.OlmPkDecryption import org.matrix.olm.OlmPkEncryption @@ -407,20 +409,22 @@ internal class DefaultKeysBackupService @Inject constructor( */ @WorkerThread private fun getKeysBackupTrustBg(keysBackupVersion: KeysVersionResult): KeysBackupVersionTrust { - val keysBackupVersionTrust = KeysBackupVersionTrust() val authData = keysBackupVersion.getAuthDataAsMegolmBackupAuthData() if (authData == null || authData.publicKey.isEmpty() || authData.signatures.isNullOrEmpty()) { Timber.v("getKeysBackupTrust: Key backup is absent or missing required data") - return keysBackupVersionTrust + return KeysBackupVersionTrust(usable = false) } val mySigs = authData.signatures[userId] if (mySigs.isNullOrEmpty()) { Timber.v("getKeysBackupTrust: Ignoring key backup because it lacks any signatures from this user") - return keysBackupVersionTrust + return KeysBackupVersionTrust(usable = false) } + var keysBackupVersionTrustIsUsable = false + val keysBackupVersionTrustSignatures = mutableListOf() + for ((keyId, mySignature) in mySigs) { // XXX: is this how we're supposed to get the device id? var deviceId: String? = null @@ -447,19 +451,23 @@ internal class DefaultKeysBackupService @Inject constructor( } if (isSignatureValid && device.isVerified) { - keysBackupVersionTrust.usable = true + keysBackupVersionTrustIsUsable = true } } - val signature = KeysBackupVersionTrustSignature() - signature.device = device - signature.valid = isSignatureValid - signature.deviceId = deviceId - keysBackupVersionTrust.signatures.add(signature) + val signature = KeysBackupVersionTrustSignature( + deviceId = deviceId, + device = device, + valid = isSignatureValid, + ) + keysBackupVersionTrustSignatures.add(signature) } } - return keysBackupVersionTrust + return KeysBackupVersionTrust( + usable = keysBackupVersionTrustIsUsable, + signatures = keysBackupVersionTrustSignatures + ) } override fun trustKeysBackupVersion(keysBackupVersion: KeysVersionResult, @@ -586,21 +594,28 @@ internal class DefaultKeysBackupService @Inject constructor( cryptoCoroutineScope.launch(coroutineDispatchers.main) { try { - val keysBackupVersion = getKeysBackupLastVersionTask.execute(Unit) - val recoveryKey = computeRecoveryKey(secret.fromBase64()) - if (isValidRecoveryKeyForKeysBackupVersion(recoveryKey, keysBackupVersion)) { - awaitCallback { - trustKeysBackupVersion(keysBackupVersion, true, it) + when (val keysBackupLastVersionResult = getKeysBackupLastVersionTask.execute(Unit)) { + KeysBackupLastVersionResult.NoKeysBackup -> { + Timber.d("No keys backup found") } - val importResult = awaitCallback { - restoreKeysWithRecoveryKey(keysBackupVersion, recoveryKey, null, null, null, it) + is KeysBackupLastVersionResult.KeysBackup -> { + val keysBackupVersion = keysBackupLastVersionResult.keysVersionResult + val recoveryKey = computeRecoveryKey(secret.fromBase64()) + if (isValidRecoveryKeyForKeysBackupVersion(recoveryKey, keysBackupVersion)) { + awaitCallback { + trustKeysBackupVersion(keysBackupVersion, true, it) + } + val importResult = awaitCallback { + restoreKeysWithRecoveryKey(keysBackupVersion, recoveryKey, null, null, null, it) + } + withContext(coroutineDispatchers.crypto) { + cryptoStore.saveBackupRecoveryKey(recoveryKey, keysBackupVersion.version) + } + Timber.i("onSecretKeyGossip: Recovered keys $importResult") + } else { + Timber.e("onSecretKeyGossip: Recovery key is not valid ${keysBackupVersion.version}") + } } - withContext(coroutineDispatchers.crypto) { - cryptoStore.saveBackupRecoveryKey(recoveryKey, keysBackupVersion.version) - } - Timber.i("onSecretKeyGossip: Recovered keys ${importResult.successfullyNumberOfImportedKeys} out of ${importResult.totalNumberOfKeys}") - } else { - Timber.e("onSecretKeyGossip: Recovery key is not valid ${keysBackupVersion.version}") } } catch (failure: Throwable) { Timber.e("onSecretKeyGossip: failed to trust key backup version ${keysBackupVersion?.version}") @@ -875,63 +890,49 @@ internal class DefaultKeysBackupService @Inject constructor( .executeBy(taskExecutor) } - override fun getCurrentVersion(callback: MatrixCallback) { + override fun getCurrentVersion(callback: MatrixCallback) { getKeysBackupLastVersionTask .configureWith { - this.callback = object : MatrixCallback { - override fun onSuccess(data: KeysVersionResult) { - callback.onSuccess(data) - } - - override fun onFailure(failure: Throwable) { - if (failure is Failure.ServerError && - failure.error.code == MatrixError.M_NOT_FOUND) { - // Workaround because the homeserver currently returns M_NOT_FOUND when there is no key backup - callback.onSuccess(null) - } else { - // Transmit the error - callback.onFailure(failure) - } - } - } + this.callback = callback } .executeBy(taskExecutor) } override fun forceUsingLastVersion(callback: MatrixCallback) { - getCurrentVersion(object : MatrixCallback { - override fun onSuccess(data: KeysVersionResult?) { + getCurrentVersion(object : MatrixCallback { + override fun onSuccess(data: KeysBackupLastVersionResult) { val localBackupVersion = keysBackupVersion?.version - val serverBackupVersion = data?.version - - if (serverBackupVersion == null) { - if (localBackupVersion == null) { - // No backup on the server, and backup is not active - callback.onSuccess(true) - } else { - // No backup on the server, and we are currently backing up, so stop backing up - callback.onSuccess(false) - resetKeysBackupData() - keysBackupVersion = null - keysBackupStateManager.state = KeysBackupState.Disabled - } - } else { - if (localBackupVersion == null) { - // backup on the server, and backup is not active - callback.onSuccess(false) - // Do a check - checkAndStartWithKeysBackupVersion(data) - } else { - // Backup on the server, and we are currently backing up, compare version - if (localBackupVersion == serverBackupVersion) { - // We are already using the last version of the backup + when (data) { + KeysBackupLastVersionResult.NoKeysBackup -> { + if (localBackupVersion == null) { + // No backup on the server, and backup is not active callback.onSuccess(true) } else { - // We are not using the last version, so delete the current version we are using on the server + // No backup on the server, and we are currently backing up, so stop backing up callback.onSuccess(false) + resetKeysBackupData() + keysBackupVersion = null + keysBackupStateManager.state = KeysBackupState.Disabled + } + } + is KeysBackupLastVersionResult.KeysBackup -> { + if (localBackupVersion == null) { + // backup on the server, and backup is not active + callback.onSuccess(false) + // Do a check + checkAndStartWithKeysBackupVersion(data.keysVersionResult) + } else { + // Backup on the server, and we are currently backing up, compare version + if (localBackupVersion == data.keysVersionResult.version) { + // We are already using the last version of the backup + callback.onSuccess(true) + } else { + // We are not using the last version, so delete the current version we are using on the server + callback.onSuccess(false) - // This will automatically check for the last version then - deleteBackup(localBackupVersion, null) + // This will automatically check for the last version then + deleteBackup(localBackupVersion, null) + } } } } @@ -954,9 +955,9 @@ internal class DefaultKeysBackupService @Inject constructor( keysBackupVersion = null keysBackupStateManager.state = KeysBackupState.CheckingBackUpOnHomeserver - getCurrentVersion(object : MatrixCallback { - override fun onSuccess(data: KeysVersionResult?) { - checkAndStartWithKeysBackupVersion(data) + getCurrentVersion(object : MatrixCallback { + override fun onSuccess(data: KeysBackupLastVersionResult) { + checkAndStartWithKeysBackupVersion(data.toKeysVersionResult()) } override fun onFailure(failure: Throwable) { @@ -1104,6 +1105,13 @@ internal class DefaultKeysBackupService @Inject constructor( } } + override fun computePrivateKey(passphrase: String, + privateKeySalt: String, + privateKeyIterations: Int, + progressListener: ProgressListener): ByteArray { + return deriveKey(passphrase, privateKeySalt, privateKeyIterations, progressListener) + } + /** * Enable backing up of keys. * This method will update the state and will start sending keys in nominal case 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 24c3942055..c12879dbee 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 @@ -30,13 +30,14 @@ import kotlin.experimental.xor private const val SALT_LENGTH = 32 private const val DEFAULT_ITERATION = 500_000 -data class GeneratePrivateKeyResult( +internal data class GeneratePrivateKeyResult( // The private key val privateKey: ByteArray, // the salt used to generate the private key val salt: String, // number of key derivations done on the generated private key. - val iterations: Int) + val iterations: Int +) /** * Compute a private key from a password. @@ -46,7 +47,9 @@ data class GeneratePrivateKeyResult( * @return a {privateKey, salt, iterations} tuple. */ @WorkerThread -fun generatePrivateKeyWithPassword(password: String, progressListener: ProgressListener?): GeneratePrivateKeyResult { +internal fun generatePrivateKeyWithPassword(password: String, + progressListener: ProgressListener? +): GeneratePrivateKeyResult { val salt = generateSalt() val iterations = DEFAULT_ITERATION val privateKey = deriveKey(password, salt, iterations, progressListener) @@ -65,10 +68,10 @@ fun generatePrivateKeyWithPassword(password: String, progressListener: ProgressL * @return a private key. */ @WorkerThread -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) } @@ -83,10 +86,10 @@ fun retrievePrivateKeyWithPassword(password: String, * @return a private key. */ @WorkerThread -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 val t0 = System.currentTimeMillis() 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 eb4c55a3e7..8464b33526 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 @@ -16,12 +16,12 @@ package org.matrix.android.sdk.internal.crypto.keysbackup.api +import org.matrix.android.sdk.api.session.crypto.keysbackup.KeysVersion +import org.matrix.android.sdk.api.session.crypto.keysbackup.KeysVersionResult import org.matrix.android.sdk.internal.crypto.keysbackup.model.rest.BackupKeysResult import org.matrix.android.sdk.internal.crypto.keysbackup.model.rest.CreateKeysBackupVersionBody import org.matrix.android.sdk.internal.crypto.keysbackup.model.rest.KeyBackupData import org.matrix.android.sdk.internal.crypto.keysbackup.model.rest.KeysBackupData -import org.matrix.android.sdk.internal.crypto.keysbackup.model.rest.KeysVersion -import org.matrix.android.sdk.internal.crypto.keysbackup.model.rest.KeysVersionResult import org.matrix.android.sdk.internal.crypto.keysbackup.model.rest.RoomKeysBackupData import org.matrix.android.sdk.internal.crypto.keysbackup.model.rest.UpdateKeysBackupVersionBody import org.matrix.android.sdk.internal.network.NetworkConstants diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/keysbackup/model/KeyBackupVersionTrust.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/keysbackup/model/KeyBackupVersionTrust.kt deleted file mode 100644 index 07ca87fe33..0000000000 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/keysbackup/model/KeyBackupVersionTrust.kt +++ /dev/null @@ -1,36 +0,0 @@ -/* - * Copyright 2020 The Matrix.org Foundation C.I.C. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.matrix.android.sdk.internal.crypto.keysbackup.model - -import com.squareup.moshi.JsonClass - -/** - * Data model for response to [KeysBackup.isKeyBackupTrusted()]. - */ -@JsonClass(generateAdapter = true) -data class KeyBackupVersionTrust( - /** - * Flag to indicate if the backup is trusted. - * true if there is a signature that is valid & from a trusted device. - */ - var usable: Boolean = false, - - /** - * Signatures found in the backup version. - */ - var signatures: MutableList = ArrayList() -) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/keysbackup/model/KeyBackupVersionTrustSignature.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/keysbackup/model/SignalableMegolmBackupAuthData.kt similarity index 56% rename from matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/keysbackup/model/KeyBackupVersionTrustSignature.kt rename to matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/keysbackup/model/SignalableMegolmBackupAuthData.kt index 5256c78176..85f75a61e2 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/keysbackup/model/KeyBackupVersionTrustSignature.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/keysbackup/model/SignalableMegolmBackupAuthData.kt @@ -16,20 +16,21 @@ package org.matrix.android.sdk.internal.crypto.keysbackup.model -import org.matrix.android.sdk.internal.crypto.model.CryptoDeviceInfo +import org.matrix.android.sdk.api.util.JsonDict -/** - * A signature in a the `KeyBackupVersionTrust` object. - */ -class KeyBackupVersionTrustSignature { +internal data class SignalableMegolmBackupAuthData( + val publicKey: String, + val privateKeySalt: String? = null, + val privateKeyIterations: Int? = null +) { + fun signalableJSONDictionary(): JsonDict = HashMap().apply { + put("public_key", publicKey) - /** - * The device that signed the backup version. - */ - var device: CryptoDeviceInfo? = null - - /** - *Flag to indicate the signature from this device is valid. - */ - var valid = false + privateKeySalt?.let { + put("private_key_salt", it) + } + privateKeyIterations?.let { + put("private_key_iterations", it) + } + } } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/keysbackup/model/rest/KeyBackupData.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/keysbackup/model/rest/KeyBackupData.kt index 3f8129b8f6..5c3d0c12b0 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/keysbackup/model/rest/KeyBackupData.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/keysbackup/model/rest/KeyBackupData.kt @@ -25,7 +25,7 @@ import org.matrix.android.sdk.internal.network.parsing.ForceToBoolean * Backup data for one key. */ @JsonClass(generateAdapter = true) -data class KeyBackupData( +internal data class KeyBackupData( /** * Required. The index of the first message in the session that the key can decrypt. */ diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/keysbackup/model/rest/KeysAlgorithmAndData.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/keysbackup/model/rest/KeysAlgorithmAndData.kt index e098aa0440..898b357c51 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/keysbackup/model/rest/KeysAlgorithmAndData.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/keysbackup/model/rest/KeysAlgorithmAndData.kt @@ -16,9 +16,9 @@ package org.matrix.android.sdk.internal.crypto.keysbackup.model.rest +import org.matrix.android.sdk.api.crypto.MXCRYPTO_ALGORITHM_MEGOLM_BACKUP +import org.matrix.android.sdk.api.session.crypto.keysbackup.MegolmBackupAuthData import org.matrix.android.sdk.api.util.JsonDict -import org.matrix.android.sdk.internal.crypto.MXCRYPTO_ALGORITHM_MEGOLM_BACKUP -import org.matrix.android.sdk.internal.crypto.keysbackup.model.MegolmBackupAuthData import org.matrix.android.sdk.internal.di.MoshiProvider /** diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/keysbackup/model/rest/KeysBackupData.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/keysbackup/model/rest/KeysBackupData.kt index 6b55f20020..4237458859 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/keysbackup/model/rest/KeysBackupData.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/keysbackup/model/rest/KeysBackupData.kt @@ -23,7 +23,7 @@ import com.squareup.moshi.JsonClass * Backup data for several keys in several rooms. */ @JsonClass(generateAdapter = true) -data class KeysBackupData( +internal data class KeysBackupData( // the keys are the room IDs, and the values are RoomKeysBackupData @Json(name = "rooms") val roomIdToRoomKeysBackupData: MutableMap = HashMap() diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/keysbackup/model/rest/RoomKeysBackupData.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/keysbackup/model/rest/RoomKeysBackupData.kt index ce42a3bc35..5ea6a2f890 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/keysbackup/model/rest/RoomKeysBackupData.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/keysbackup/model/rest/RoomKeysBackupData.kt @@ -23,7 +23,7 @@ import com.squareup.moshi.JsonClass * Backup data for several keys within a room. */ @JsonClass(generateAdapter = true) -data class RoomKeysBackupData( +internal data class RoomKeysBackupData( // the keys are the session IDs, and the values are KeyBackupData @Json(name = "sessions") val sessionIdToKeyBackupData: MutableMap = HashMap() diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/keysbackup/model/rest/UpdateKeysBackupVersionBody.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/keysbackup/model/rest/UpdateKeysBackupVersionBody.kt index 4512ed7a55..3f2def84d5 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/keysbackup/model/rest/UpdateKeysBackupVersionBody.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/keysbackup/model/rest/UpdateKeysBackupVersionBody.kt @@ -21,7 +21,7 @@ import com.squareup.moshi.JsonClass import org.matrix.android.sdk.api.util.JsonDict @JsonClass(generateAdapter = true) -data class UpdateKeysBackupVersionBody( +internal data class UpdateKeysBackupVersionBody( /** * The algorithm used for storing backups. Currently, only "m.megolm_backup.v1.curve25519-aes-sha2" is defined */ diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/keysbackup/tasks/CreateKeysBackupVersionTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/keysbackup/tasks/CreateKeysBackupVersionTask.kt index 62610a0b7b..10d6e923e7 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/keysbackup/tasks/CreateKeysBackupVersionTask.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/keysbackup/tasks/CreateKeysBackupVersionTask.kt @@ -16,9 +16,9 @@ package org.matrix.android.sdk.internal.crypto.keysbackup.tasks +import org.matrix.android.sdk.api.session.crypto.keysbackup.KeysVersion import org.matrix.android.sdk.internal.crypto.keysbackup.api.RoomKeysApi import org.matrix.android.sdk.internal.crypto.keysbackup.model.rest.CreateKeysBackupVersionBody -import org.matrix.android.sdk.internal.crypto.keysbackup.model.rest.KeysVersion import org.matrix.android.sdk.internal.network.GlobalErrorReceiver import org.matrix.android.sdk.internal.network.executeRequest import org.matrix.android.sdk.internal.task.Task diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/keysbackup/tasks/GetKeysBackupLastVersionTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/keysbackup/tasks/GetKeysBackupLastVersionTask.kt index 54dbf85e30..e5621c0cb5 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/keysbackup/tasks/GetKeysBackupLastVersionTask.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/keysbackup/tasks/GetKeysBackupLastVersionTask.kt @@ -16,23 +16,34 @@ package org.matrix.android.sdk.internal.crypto.keysbackup.tasks +import org.matrix.android.sdk.api.failure.is404 +import org.matrix.android.sdk.api.session.crypto.keysbackup.KeysBackupLastVersionResult import org.matrix.android.sdk.internal.crypto.keysbackup.api.RoomKeysApi -import org.matrix.android.sdk.internal.crypto.keysbackup.model.rest.KeysVersionResult import org.matrix.android.sdk.internal.network.GlobalErrorReceiver import org.matrix.android.sdk.internal.network.executeRequest import org.matrix.android.sdk.internal.task.Task import javax.inject.Inject -internal interface GetKeysBackupLastVersionTask : Task +internal interface GetKeysBackupLastVersionTask : Task internal class DefaultGetKeysBackupLastVersionTask @Inject constructor( private val roomKeysApi: RoomKeysApi, private val globalErrorReceiver: GlobalErrorReceiver ) : GetKeysBackupLastVersionTask { - override suspend fun execute(params: Unit): KeysVersionResult { - return executeRequest(globalErrorReceiver) { - roomKeysApi.getKeysBackupLastVersion() + override suspend fun execute(params: Unit): KeysBackupLastVersionResult { + return try { + val keysVersionResult = executeRequest(globalErrorReceiver) { + roomKeysApi.getKeysBackupLastVersion() + } + KeysBackupLastVersionResult.KeysBackup(keysVersionResult) + } catch (throwable: Throwable) { + if (throwable.is404()) { + KeysBackupLastVersionResult.NoKeysBackup + } else { + // Propagate other errors + throw throwable + } } } } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/keysbackup/tasks/GetKeysBackupVersionTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/keysbackup/tasks/GetKeysBackupVersionTask.kt index 390873eb68..fe1ca29798 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/keysbackup/tasks/GetKeysBackupVersionTask.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/keysbackup/tasks/GetKeysBackupVersionTask.kt @@ -16,8 +16,8 @@ package org.matrix.android.sdk.internal.crypto.keysbackup.tasks +import org.matrix.android.sdk.api.session.crypto.keysbackup.KeysVersionResult import org.matrix.android.sdk.internal.crypto.keysbackup.api.RoomKeysApi -import org.matrix.android.sdk.internal.crypto.keysbackup.model.rest.KeysVersionResult import org.matrix.android.sdk.internal.network.GlobalErrorReceiver import org.matrix.android.sdk.internal.network.executeRequest import org.matrix.android.sdk.internal.task.Task diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/keysbackup/util/Base58.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/keysbackup/util/Base58.kt index def9c1b675..0e746f289b 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/keysbackup/util/Base58.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/keysbackup/util/Base58.kt @@ -41,7 +41,7 @@ private val BASE = BigInteger.valueOf(58) /** * Encode a byte array to a human readable string with base58 chars */ -fun base58encode(input: ByteArray): String { +internal fun base58encode(input: ByteArray): String { var bi = BigInteger(1, input) val s = StringBuffer() while (bi >= BASE) { @@ -64,7 +64,7 @@ fun base58encode(input: ByteArray): String { /** * Decode a base58 String to a byte array */ -fun base58decode(input: String): ByteArray { +internal fun base58decode(input: String): ByteArray { var result = decodeToBigInteger(input).toByteArray() // Remove the first leading zero if any diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/model/CryptoDeviceInfo.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/model/CryptoDeviceInfo.kt index b3638dc414..727f739866 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/model/CryptoDeviceInfo.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/model/CryptoDeviceInfo.kt @@ -15,64 +15,8 @@ */ package org.matrix.android.sdk.internal.crypto.model -import org.matrix.android.sdk.internal.crypto.crosssigning.DeviceTrustLevel +import org.matrix.android.sdk.api.session.crypto.model.CryptoDeviceInfo import org.matrix.android.sdk.internal.crypto.model.rest.DeviceKeys -import org.matrix.android.sdk.internal.crypto.model.rest.UnsignedDeviceInfo - -data class CryptoDeviceInfo( - val deviceId: String, - override val userId: String, - var algorithms: List? = null, - override val keys: Map? = null, - override val signatures: Map>? = null, - val unsigned: UnsignedDeviceInfo? = null, - var trustLevel: DeviceTrustLevel? = null, - var isBlocked: Boolean = false, - val firstTimeSeenLocalTs: Long? = null -) : CryptoInfo { - - val isVerified: Boolean - get() = trustLevel?.isVerified() == true - - val isUnknown: Boolean - get() = trustLevel == null - - /** - * @return the fingerprint - */ - fun fingerprint(): String? { - return keys - ?.takeIf { deviceId.isNotBlank() } - ?.get("ed25519:$deviceId") - } - - /** - * @return the identity key - */ - fun identityKey(): String? { - return keys - ?.takeIf { deviceId.isNotBlank() } - ?.get("curve25519:$deviceId") - } - - /** - * @return the display name - */ - fun displayName(): String? { - return unsigned?.deviceDisplayName - } - - override fun signalableJSONDictionary(): Map { - val map = HashMap() - map["device_id"] = deviceId - map["user_id"] = userId - algorithms?.let { map["algorithms"] = it } - keys?.let { map["keys"] = it } - return map - } - - fun shortDebugString() = "$userId|$deviceId" -} internal fun CryptoDeviceInfo.toRest(): DeviceKeys { return CryptoInfoMapper.map(this) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/model/CryptoInfo.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/model/CryptoInfo.kt index 39981e01f7..e49f17262c 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/model/CryptoInfo.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/model/CryptoInfo.kt @@ -20,7 +20,7 @@ package org.matrix.android.sdk.internal.crypto.model * Generic crypto info. * Can be a device (CryptoDeviceInfo), as well as a CryptoCrossSigningInfo (can be seen as a kind of virtual device) */ -interface CryptoInfo { +internal interface CryptoInfo { val userId: String diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/model/CryptoInfoMapper.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/model/CryptoInfoMapper.kt index 6cc6f5400f..de9b3f24ff 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/model/CryptoInfoMapper.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/model/CryptoInfoMapper.kt @@ -15,6 +15,8 @@ */ package org.matrix.android.sdk.internal.crypto.model +import org.matrix.android.sdk.api.session.crypto.crosssigning.CryptoCrossSigningKey +import org.matrix.android.sdk.api.session.crypto.model.CryptoDeviceInfo import org.matrix.android.sdk.internal.crypto.model.rest.DeviceKeys import org.matrix.android.sdk.internal.crypto.model.rest.DeviceKeysWithUnsigned import org.matrix.android.sdk.internal.crypto.model.rest.RestKeyInfo diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/model/MXKey.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/model/MXKey.kt index 9f425eee7f..cff713bf8f 100755 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/model/MXKey.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/model/MXKey.kt @@ -19,7 +19,7 @@ package org.matrix.android.sdk.internal.crypto.model import org.matrix.android.sdk.api.util.JsonDict import timber.log.Timber -data class MXKey( +internal data class MXKey( /** * The type of the key (in the example: "signed_curve25519"). */ diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/model/MXOlmSessionResult.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/model/MXOlmSessionResult.kt index b07a08c30f..671827799e 100755 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/model/MXOlmSessionResult.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/model/MXOlmSessionResult.kt @@ -16,9 +16,10 @@ package org.matrix.android.sdk.internal.crypto.model +import org.matrix.android.sdk.api.session.crypto.model.CryptoDeviceInfo import java.io.Serializable -data class MXOlmSessionResult( +internal data class MXOlmSessionResult( /** * the device */ @@ -27,4 +28,5 @@ data class MXOlmSessionResult( * Base64 olm session id. * null if no session could be established. */ - var sessionId: String?) : Serializable + var sessionId: String? +) : Serializable diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/model/MXUsersDevicesMap.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/model/MXUsersDevicesMap.kt index 662541428e..58aff14a3d 100755 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/model/MXUsersDevicesMap.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/model/MXUsersDevicesMap.kt @@ -16,121 +16,9 @@ package org.matrix.android.sdk.internal.crypto.model -class MXUsersDevicesMap { +import org.matrix.android.sdk.api.session.crypto.model.MXUsersDevicesMap - // A map of maps (userId -> (deviceId -> Object)). - val map = HashMap>() - - /** - * @return the user Ids - */ - val userIds: List - get() = map.keys.toList() - - val isEmpty: Boolean - get() = map.isEmpty() - - /** - * Provides the device ids list for a user id - * FIXME Should maybe return emptyList and not null, to avoid many !! in the code - * - * @param userId the user id - * @return the device ids list - */ - fun getUserDeviceIds(userId: String?): List? { - return if (!userId.isNullOrBlank() && map.containsKey(userId)) { - map[userId]!!.keys.toList() - } else null - } - - /** - * Provides the object for a device id and a user Id - * - * @param deviceId the device id - * @param userId the object id - * @return the object - */ - fun getObject(userId: String?, deviceId: String?): E? { - return if (!userId.isNullOrBlank() && !deviceId.isNullOrBlank()) { - map[userId]?.get(deviceId) - } else null - } - - /** - * Set an object for a dedicated user Id and device Id - * - * @param userId the user Id - * @param deviceId the device id - * @param o the object to set - */ - fun setObject(userId: String?, deviceId: String?, o: E?) { - if (null != o && userId?.isNotBlank() == true && deviceId?.isNotBlank() == true) { - val devices = map.getOrPut(userId) { HashMap() } - devices[deviceId] = o - } - } - - /** - * Defines the objects map for a user Id - * - * @param objectsPerDevices the objects maps - * @param userId the user id - */ - fun setObjects(userId: String?, objectsPerDevices: Map?) { - if (!userId.isNullOrBlank()) { - if (null == objectsPerDevices) { - map.remove(userId) - } else { - map[userId] = HashMap(objectsPerDevices) - } - } - } - - /** - * Removes objects for a dedicated user - * - * @param userId the user id. - */ - fun removeUserObjects(userId: String?) { - if (!userId.isNullOrBlank()) { - map.remove(userId) - } - } - - /** - * Clear the internal dictionary - */ - fun removeAllObjects() { - map.clear() - } - - /** - * Add entries from another MXUsersDevicesMap - * - * @param other the other one - */ - fun addEntriesFromMap(other: MXUsersDevicesMap?) { - if (null != other) { - map.putAll(other.map) - } - } - - override fun toString(): String { - return "MXUsersDevicesMap $map" - } -} - -inline fun MXUsersDevicesMap.forEach(action: (String, String, T) -> Unit) { - userIds.forEach { userId -> - getUserDeviceIds(userId)?.forEach { deviceId -> - getObject(userId, deviceId)?.let { - action(userId, deviceId, it) - } - } - } -} - -internal fun MXUsersDevicesMap.toDebugString() = +internal fun MXUsersDevicesMap.toDebugString() = map.entries.joinToString { "${it.key} [${it.value.keys.joinToString { it }}]" } internal fun MXUsersDevicesMap.toDebugCount() = diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/model/OlmInboundGroupSessionWrapper.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/model/OlmInboundGroupSessionWrapper.kt index 086a236a2b..45ffcc6606 100755 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/model/OlmInboundGroupSessionWrapper.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/model/OlmInboundGroupSessionWrapper.kt @@ -16,7 +16,7 @@ package org.matrix.android.sdk.internal.crypto.model -import org.matrix.android.sdk.internal.crypto.MXCRYPTO_ALGORITHM_MEGOLM +import org.matrix.android.sdk.api.crypto.MXCRYPTO_ALGORITHM_MEGOLM import org.matrix.android.sdk.internal.crypto.MegolmSessionData import org.matrix.olm.OlmInboundGroupSession import timber.log.Timber @@ -26,7 +26,7 @@ import java.io.Serializable * This class adds more context to a OlmInboundGroupSession object. * This allows additional checks. The class implements Serializable so that the context can be stored. */ -class OlmInboundGroupSessionWrapper : Serializable { +internal class OlmInboundGroupSessionWrapper : Serializable { // The associated olm inbound group session. var olmInboundGroupSession: OlmInboundGroupSession? = null diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/model/OlmInboundGroupSessionWrapper2.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/model/OlmInboundGroupSessionWrapper2.kt index 1dc27c75ca..1f671aa896 100755 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/model/OlmInboundGroupSessionWrapper2.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/model/OlmInboundGroupSessionWrapper2.kt @@ -16,7 +16,7 @@ package org.matrix.android.sdk.internal.crypto.model -import org.matrix.android.sdk.internal.crypto.MXCRYPTO_ALGORITHM_MEGOLM +import org.matrix.android.sdk.api.crypto.MXCRYPTO_ALGORITHM_MEGOLM import org.matrix.android.sdk.internal.crypto.MegolmSessionData import org.matrix.olm.OlmInboundGroupSession import timber.log.Timber @@ -26,7 +26,7 @@ import java.io.Serializable * This class adds more context to a OlmInboundGroupSession object. * This allows additional checks. The class implements Serializable so that the context can be stored. */ -class OlmInboundGroupSessionWrapper2 : Serializable { +internal class OlmInboundGroupSessionWrapper2 : Serializable { // The associated olm inbound group session. var olmInboundGroupSession: OlmInboundGroupSession? = null diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/model/OlmSessionWrapper.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/model/OlmSessionWrapper.kt index 263cb3b036..927d049eca 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/model/OlmSessionWrapper.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/model/OlmSessionWrapper.kt @@ -22,7 +22,7 @@ import org.matrix.olm.OlmSession /** * Encapsulate a OlmSession and a last received message Timestamp */ -data class OlmSessionWrapper( +internal data class OlmSessionWrapper( // The associated olm session. val olmSession: OlmSession, // Timestamp at which the session last received a message. diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/model/OutboundGroupSessionWrapper.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/model/OutboundGroupSessionWrapper.kt index b616284e14..4ac87f44ce 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/model/OutboundGroupSessionWrapper.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/model/OutboundGroupSessionWrapper.kt @@ -18,7 +18,7 @@ package org.matrix.android.sdk.internal.crypto.model import org.matrix.olm.OlmOutboundGroupSession -data class OutboundGroupSessionWrapper( +internal data class OutboundGroupSessionWrapper( val outboundGroupSession: OlmOutboundGroupSession, val creationTime: Long ) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/model/rest/DeviceKeys.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/model/rest/DeviceKeys.kt index 3a845b1f2a..611d9b72b5 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/model/rest/DeviceKeys.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/model/rest/DeviceKeys.kt @@ -20,7 +20,7 @@ import com.squareup.moshi.Json import com.squareup.moshi.JsonClass @JsonClass(generateAdapter = true) -data class DeviceKeys( +internal data class DeviceKeys( /** * Required. The ID of the user the device belongs to. Must match the user ID used when logging in. */ diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/model/rest/DeviceKeysWithUnsigned.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/model/rest/DeviceKeysWithUnsigned.kt index 35fce32394..32f577c99b 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/model/rest/DeviceKeysWithUnsigned.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/model/rest/DeviceKeysWithUnsigned.kt @@ -18,9 +18,10 @@ package org.matrix.android.sdk.internal.crypto.model.rest import com.squareup.moshi.Json import com.squareup.moshi.JsonClass +import org.matrix.android.sdk.api.session.crypto.model.UnsignedDeviceInfo @JsonClass(generateAdapter = true) -data class DeviceKeysWithUnsigned( +internal data class DeviceKeysWithUnsigned( /** * Required. The ID of the user the device belongs to. Must match the user ID used when logging in. */ diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/model/rest/EncryptedMessage.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/model/rest/EncryptedMessage.kt index f32676a919..c2f76f9dea 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/model/rest/EncryptedMessage.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/model/rest/EncryptedMessage.kt @@ -18,9 +18,10 @@ package org.matrix.android.sdk.internal.crypto.model.rest import com.squareup.moshi.Json import com.squareup.moshi.JsonClass +import org.matrix.android.sdk.api.session.crypto.model.SendToDeviceObject @JsonClass(generateAdapter = true) -data class EncryptedMessage( +internal data class EncryptedMessage( @Json(name = "algorithm") val algorithm: String? = null, diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/model/rest/GossipingDefaultContent.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/model/rest/GossipingDefaultContent.kt new file mode 100644 index 0000000000..8f789a638c --- /dev/null +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/model/rest/GossipingDefaultContent.kt @@ -0,0 +1,27 @@ +/* + * 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.model.rest + +import com.squareup.moshi.Json +import com.squareup.moshi.JsonClass +import org.matrix.android.sdk.api.session.crypto.model.GossipingToDeviceObject + +@JsonClass(generateAdapter = true) +internal data class GossipingDefaultContent( + @Json(name = "action") override val action: String?, + @Json(name = "requesting_device_id") override val requestingDeviceId: String?, + @Json(name = "m.request_id") override val requestId: String? = null +) : GossipingToDeviceObject 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 f695425c2a..7a5773bf24 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 @@ -17,6 +17,7 @@ package org.matrix.android.sdk.internal.crypto.model.rest import com.squareup.moshi.Json import com.squareup.moshi.JsonClass +import org.matrix.android.sdk.api.session.crypto.model.SendToDeviceObject import org.matrix.android.sdk.internal.crypto.verification.VerificationInfoAccept import org.matrix.android.sdk.internal.crypto.verification.VerificationInfoAcceptFactory diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/model/rest/KeyVerificationCancel.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/model/rest/KeyVerificationCancel.kt index 4dfa5984df..90272bf0e4 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/model/rest/KeyVerificationCancel.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/model/rest/KeyVerificationCancel.kt @@ -17,6 +17,7 @@ package org.matrix.android.sdk.internal.crypto.model.rest import com.squareup.moshi.Json import com.squareup.moshi.JsonClass +import org.matrix.android.sdk.api.session.crypto.model.SendToDeviceObject import org.matrix.android.sdk.api.session.crypto.verification.CancelCode import org.matrix.android.sdk.internal.crypto.verification.VerificationInfoCancel diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/model/rest/KeyVerificationDone.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/model/rest/KeyVerificationDone.kt index 96afba060b..e3907914ac 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/model/rest/KeyVerificationDone.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/model/rest/KeyVerificationDone.kt @@ -17,6 +17,7 @@ package org.matrix.android.sdk.internal.crypto.model.rest import com.squareup.moshi.Json import com.squareup.moshi.JsonClass +import org.matrix.android.sdk.api.session.crypto.model.SendToDeviceObject import org.matrix.android.sdk.internal.crypto.verification.VerificationInfoDone /** diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/model/rest/KeyVerificationKey.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/model/rest/KeyVerificationKey.kt index 7ded437cea..19d8c32ddf 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/model/rest/KeyVerificationKey.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/model/rest/KeyVerificationKey.kt @@ -17,6 +17,7 @@ package org.matrix.android.sdk.internal.crypto.model.rest import com.squareup.moshi.Json import com.squareup.moshi.JsonClass +import org.matrix.android.sdk.api.session.crypto.model.SendToDeviceObject import org.matrix.android.sdk.internal.crypto.verification.VerificationInfoKey import org.matrix.android.sdk.internal.crypto.verification.VerificationInfoKeyFactory diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/model/rest/KeyVerificationMac.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/model/rest/KeyVerificationMac.kt index 6c055aee2a..5335428c0f 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/model/rest/KeyVerificationMac.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/model/rest/KeyVerificationMac.kt @@ -17,6 +17,7 @@ package org.matrix.android.sdk.internal.crypto.model.rest import com.squareup.moshi.Json import com.squareup.moshi.JsonClass +import org.matrix.android.sdk.api.session.crypto.model.SendToDeviceObject import org.matrix.android.sdk.internal.crypto.verification.VerificationInfoMac import org.matrix.android.sdk.internal.crypto.verification.VerificationInfoMacFactory diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/model/rest/KeyVerificationReady.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/model/rest/KeyVerificationReady.kt index 3562613c2e..e6770be9a0 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/model/rest/KeyVerificationReady.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/model/rest/KeyVerificationReady.kt @@ -17,6 +17,7 @@ package org.matrix.android.sdk.internal.crypto.model.rest import com.squareup.moshi.Json import com.squareup.moshi.JsonClass +import org.matrix.android.sdk.api.session.crypto.model.SendToDeviceObject import org.matrix.android.sdk.internal.crypto.verification.VerificationInfoReady /** diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/model/rest/KeyVerificationRequest.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/model/rest/KeyVerificationRequest.kt index c30b2a306f..191d5abb60 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/model/rest/KeyVerificationRequest.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/model/rest/KeyVerificationRequest.kt @@ -17,6 +17,7 @@ package org.matrix.android.sdk.internal.crypto.model.rest import com.squareup.moshi.Json import com.squareup.moshi.JsonClass +import org.matrix.android.sdk.api.session.crypto.model.SendToDeviceObject import org.matrix.android.sdk.internal.crypto.verification.VerificationInfoRequest /** diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/model/rest/KeyVerificationStart.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/model/rest/KeyVerificationStart.kt index 52a66a9db6..f74bad844d 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/model/rest/KeyVerificationStart.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/model/rest/KeyVerificationStart.kt @@ -17,6 +17,7 @@ package org.matrix.android.sdk.internal.crypto.model.rest import com.squareup.moshi.Json import com.squareup.moshi.JsonClass +import org.matrix.android.sdk.api.session.crypto.model.SendToDeviceObject import org.matrix.android.sdk.internal.crypto.verification.VerificationInfoStart import org.matrix.android.sdk.internal.util.JsonCanonicalizer diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/model/rest/RestKeyInfo.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/model/rest/RestKeyInfo.kt index 0d41e5b648..66247d07d1 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/model/rest/RestKeyInfo.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/model/rest/RestKeyInfo.kt @@ -17,7 +17,7 @@ package org.matrix.android.sdk.internal.crypto.model.rest import com.squareup.moshi.Json import com.squareup.moshi.JsonClass -import org.matrix.android.sdk.internal.crypto.model.CryptoCrossSigningKey +import org.matrix.android.sdk.api.session.crypto.crosssigning.CryptoCrossSigningKey import org.matrix.android.sdk.internal.crypto.model.CryptoInfoMapper @JsonClass(generateAdapter = true) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/model/rest/ShareRequestCancellation.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/model/rest/ShareRequestCancellation.kt index 820ff69746..a96534fc3a 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/model/rest/ShareRequestCancellation.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/model/rest/ShareRequestCancellation.kt @@ -17,7 +17,8 @@ package org.matrix.android.sdk.internal.crypto.model.rest import com.squareup.moshi.Json import com.squareup.moshi.JsonClass -import org.matrix.android.sdk.internal.crypto.model.rest.GossipingToDeviceObject.Companion.ACTION_SHARE_CANCELLATION +import org.matrix.android.sdk.api.session.crypto.model.GossipingToDeviceObject +import org.matrix.android.sdk.api.session.crypto.model.GossipingToDeviceObject.Companion.ACTION_SHARE_CANCELLATION /** * Class representing a room key request cancellation content diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/model/rest/UploadSignatureQueryBuilder.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/model/rest/UploadSignatureQueryBuilder.kt index 1347c2f4b6..dd0ce47cd3 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/model/rest/UploadSignatureQueryBuilder.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/model/rest/UploadSignatureQueryBuilder.kt @@ -15,8 +15,9 @@ */ package org.matrix.android.sdk.internal.crypto.model.rest -import org.matrix.android.sdk.internal.crypto.model.CryptoCrossSigningKey -import org.matrix.android.sdk.internal.crypto.model.CryptoDeviceInfo +import org.matrix.android.sdk.api.session.crypto.crosssigning.CryptoCrossSigningKey +import org.matrix.android.sdk.api.session.crypto.crosssigning.toRest +import org.matrix.android.sdk.api.session.crypto.model.CryptoDeviceInfo import org.matrix.android.sdk.internal.crypto.model.toRest /** 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 e6d8b5e84f..972c03e92a 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 @@ -19,9 +19,12 @@ package org.matrix.android.sdk.internal.crypto.secrets import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.withContext import org.matrix.android.sdk.api.MatrixCoroutineDispatchers +import org.matrix.android.sdk.api.crypto.SSSS_ALGORITHM_AES_HMAC_SHA2 +import org.matrix.android.sdk.api.crypto.SSSS_ALGORITHM_CURVE25519_AES_SHA2 import org.matrix.android.sdk.api.extensions.orFalse import org.matrix.android.sdk.api.listeners.ProgressListener import org.matrix.android.sdk.api.session.accountdata.SessionAccountDataService +import org.matrix.android.sdk.api.session.crypto.keysbackup.computeRecoveryKey import org.matrix.android.sdk.api.session.events.model.toContent import org.matrix.android.sdk.api.session.securestorage.EncryptedSecretContent import org.matrix.android.sdk.api.session.securestorage.IntegrityResult @@ -35,13 +38,10 @@ import org.matrix.android.sdk.api.session.securestorage.SharedSecretStorageServi import org.matrix.android.sdk.api.session.securestorage.SsssKeyCreationInfo import org.matrix.android.sdk.api.session.securestorage.SsssKeySpec import org.matrix.android.sdk.api.session.securestorage.SsssPassphrase +import org.matrix.android.sdk.api.util.fromBase64 +import org.matrix.android.sdk.api.util.toBase64NoPadding import org.matrix.android.sdk.internal.crypto.OutgoingGossipingRequestManager -import org.matrix.android.sdk.internal.crypto.SSSS_ALGORITHM_AES_HMAC_SHA2 -import org.matrix.android.sdk.internal.crypto.SSSS_ALGORITHM_CURVE25519_AES_SHA2 -import org.matrix.android.sdk.internal.crypto.crosssigning.fromBase64 -import org.matrix.android.sdk.internal.crypto.crosssigning.toBase64NoPadding import org.matrix.android.sdk.internal.crypto.keysbackup.generatePrivateKeyWithPassword -import org.matrix.android.sdk.internal.crypto.keysbackup.util.computeRecoveryKey import org.matrix.android.sdk.internal.crypto.tools.HkdfSha256 import org.matrix.android.sdk.internal.crypto.tools.withOlmDecryption import org.matrix.android.sdk.internal.di.UserId @@ -174,7 +174,7 @@ internal class DefaultSharedSecretStorageService @Inject constructor( throw SharedSecretStorageError.UnknownAlgorithm(key.keyInfo.content.algorithm ?: "") } } - is KeyInfoResult.Error -> throw key.error + is KeyInfoResult.Error -> throw key.error } } 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 e662ff74e7..8bedb78808 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 @@ -18,25 +18,27 @@ package org.matrix.android.sdk.internal.crypto.store import androidx.lifecycle.LiveData import androidx.paging.PagedList +import org.matrix.android.sdk.api.session.crypto.NewSessionListener +import org.matrix.android.sdk.api.session.crypto.crosssigning.CryptoCrossSigningKey import org.matrix.android.sdk.api.session.crypto.crosssigning.MXCrossSigningInfo +import org.matrix.android.sdk.api.session.crypto.crosssigning.PrivateKeysInfo +import org.matrix.android.sdk.api.session.crypto.keysbackup.SavedKeyBackupKeyInfo +import org.matrix.android.sdk.api.session.crypto.model.CryptoDeviceInfo +import org.matrix.android.sdk.api.session.crypto.model.DeviceInfo +import org.matrix.android.sdk.api.session.crypto.model.GossipingRequestState +import org.matrix.android.sdk.api.session.crypto.model.IncomingRoomKeyRequest +import org.matrix.android.sdk.api.session.crypto.model.MXUsersDevicesMap +import org.matrix.android.sdk.api.session.crypto.model.OutgoingGossipingRequestState +import org.matrix.android.sdk.api.session.crypto.model.OutgoingRoomKeyRequest +import org.matrix.android.sdk.api.session.crypto.model.RoomKeyRequestBody import org.matrix.android.sdk.api.session.events.model.Event +import org.matrix.android.sdk.api.session.events.model.content.RoomKeyWithHeldContent import org.matrix.android.sdk.api.util.Optional -import org.matrix.android.sdk.internal.crypto.GossipingRequestState -import org.matrix.android.sdk.internal.crypto.IncomingRoomKeyRequest import org.matrix.android.sdk.internal.crypto.IncomingShareRequestCommon -import org.matrix.android.sdk.internal.crypto.NewSessionListener -import org.matrix.android.sdk.internal.crypto.OutgoingGossipingRequestState -import org.matrix.android.sdk.internal.crypto.OutgoingRoomKeyRequest import org.matrix.android.sdk.internal.crypto.OutgoingSecretRequest -import org.matrix.android.sdk.internal.crypto.model.CryptoCrossSigningKey -import org.matrix.android.sdk.internal.crypto.model.CryptoDeviceInfo -import org.matrix.android.sdk.internal.crypto.model.MXUsersDevicesMap import org.matrix.android.sdk.internal.crypto.model.OlmInboundGroupSessionWrapper2 import org.matrix.android.sdk.internal.crypto.model.OlmSessionWrapper import org.matrix.android.sdk.internal.crypto.model.OutboundGroupSessionWrapper -import org.matrix.android.sdk.internal.crypto.model.event.RoomKeyWithHeldContent -import org.matrix.android.sdk.internal.crypto.model.rest.DeviceInfo -import org.matrix.android.sdk.internal.crypto.model.rest.RoomKeyRequestBody import org.matrix.android.sdk.internal.crypto.store.db.model.KeysBackupDataEntity import org.matrix.olm.OlmAccount import org.matrix.olm.OlmOutboundGroupSession diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/db/Helper.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/db/Helper.kt index 493e7fbc39..b841e9c6e5 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/db/Helper.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/db/Helper.kt @@ -28,7 +28,7 @@ import java.util.zip.GZIPOutputStream /** * Get realm, invoke the action, close realm, and return the result of the action */ -fun doWithRealm(realmConfiguration: RealmConfiguration, action: (Realm) -> T): T { +internal fun doWithRealm(realmConfiguration: RealmConfiguration, action: (Realm) -> T): T { return Realm.getInstance(realmConfiguration).use { realm -> action.invoke(realm) } @@ -37,7 +37,7 @@ fun doWithRealm(realmConfiguration: RealmConfiguration, action: (Realm) -> T /** * Get realm, do the query, copy from realm, close realm, and return the copied result */ -fun doRealmQueryAndCopy(realmConfiguration: RealmConfiguration, action: (Realm) -> T?): T? { +internal fun doRealmQueryAndCopy(realmConfiguration: RealmConfiguration, action: (Realm) -> T?): T? { return Realm.getInstance(realmConfiguration).use { realm -> action.invoke(realm)?.let { realm.copyFromRealm(it) } } @@ -46,7 +46,7 @@ fun doRealmQueryAndCopy(realmConfiguration: RealmConfiguration /** * Get realm, do the list query, copy from realm, close realm, and return the copied result */ -fun doRealmQueryAndCopyList(realmConfiguration: RealmConfiguration, action: (Realm) -> Iterable): Iterable { +internal fun doRealmQueryAndCopyList(realmConfiguration: RealmConfiguration, action: (Realm) -> Iterable): Iterable { return Realm.getInstance(realmConfiguration).use { realm -> action.invoke(realm).let { realm.copyFromRealm(it) } } @@ -55,13 +55,13 @@ fun doRealmQueryAndCopyList(realmConfiguration: RealmConfigura /** * Get realm instance, invoke the action in a transaction and close realm */ -fun doRealmTransaction(realmConfiguration: RealmConfiguration, action: (Realm) -> Unit) { +internal fun doRealmTransaction(realmConfiguration: RealmConfiguration, action: (Realm) -> Unit) { Realm.getInstance(realmConfiguration).use { realm -> realm.executeTransaction { action.invoke(it) } } } -fun doRealmTransactionAsync(realmConfiguration: RealmConfiguration, action: (Realm) -> Unit) { +internal fun doRealmTransactionAsync(realmConfiguration: RealmConfiguration, action: (Realm) -> Unit) { Realm.getInstance(realmConfiguration).use { realm -> realm.executeTransactionAsync { action.invoke(it) } } @@ -70,7 +70,7 @@ fun doRealmTransactionAsync(realmConfiguration: RealmConfiguration, action: (Rea /** * Serialize any Serializable object, zip it and convert to Base64 String */ -fun serializeForRealm(o: Any?): String? { +internal fun serializeForRealm(o: Any?): String? { if (o == null) { return null } @@ -88,7 +88,7 @@ fun serializeForRealm(o: Any?): String? { * Do the opposite of serializeForRealm. */ @Suppress("UNCHECKED_CAST") -fun deserializeFromRealm(string: String?): T? { +internal fun deserializeFromRealm(string: String?): T? { if (string == null) { return null } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/db/RealmCryptoStore.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/db/RealmCryptoStore.kt index 585b3d2d25..99adbbfbae 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 @@ -25,35 +25,35 @@ import io.realm.Realm import io.realm.RealmConfiguration import io.realm.Sort import io.realm.kotlin.where +import org.matrix.android.sdk.api.crypto.MXCRYPTO_ALGORITHM_MEGOLM import org.matrix.android.sdk.api.extensions.tryOrNull +import org.matrix.android.sdk.api.session.crypto.NewSessionListener +import org.matrix.android.sdk.api.session.crypto.crosssigning.CryptoCrossSigningKey import org.matrix.android.sdk.api.session.crypto.crosssigning.MXCrossSigningInfo +import org.matrix.android.sdk.api.session.crypto.crosssigning.PrivateKeysInfo +import org.matrix.android.sdk.api.session.crypto.keysbackup.SavedKeyBackupKeyInfo +import org.matrix.android.sdk.api.session.crypto.model.CryptoDeviceInfo +import org.matrix.android.sdk.api.session.crypto.model.DeviceInfo +import org.matrix.android.sdk.api.session.crypto.model.GossipingRequestState +import org.matrix.android.sdk.api.session.crypto.model.IncomingRoomKeyRequest +import org.matrix.android.sdk.api.session.crypto.model.IncomingSecretShareRequest +import org.matrix.android.sdk.api.session.crypto.model.MXUsersDevicesMap +import org.matrix.android.sdk.api.session.crypto.model.OlmDecryptionResult +import org.matrix.android.sdk.api.session.crypto.model.OutgoingGossipingRequestState +import org.matrix.android.sdk.api.session.crypto.model.OutgoingRoomKeyRequest +import org.matrix.android.sdk.api.session.crypto.model.RoomKeyRequestBody import org.matrix.android.sdk.api.session.events.model.Event +import org.matrix.android.sdk.api.session.events.model.content.RoomKeyWithHeldContent import org.matrix.android.sdk.api.session.room.send.SendState import org.matrix.android.sdk.api.util.Optional import org.matrix.android.sdk.api.util.toOptional import org.matrix.android.sdk.internal.crypto.GossipRequestType -import org.matrix.android.sdk.internal.crypto.GossipingRequestState -import org.matrix.android.sdk.internal.crypto.IncomingRoomKeyRequest -import org.matrix.android.sdk.internal.crypto.IncomingSecretShareRequest import org.matrix.android.sdk.internal.crypto.IncomingShareRequestCommon -import org.matrix.android.sdk.internal.crypto.MXCRYPTO_ALGORITHM_MEGOLM -import org.matrix.android.sdk.internal.crypto.NewSessionListener -import org.matrix.android.sdk.internal.crypto.OutgoingGossipingRequestState -import org.matrix.android.sdk.internal.crypto.OutgoingRoomKeyRequest import org.matrix.android.sdk.internal.crypto.OutgoingSecretRequest -import org.matrix.android.sdk.internal.crypto.algorithms.olm.OlmDecryptionResult -import org.matrix.android.sdk.internal.crypto.model.CryptoCrossSigningKey -import org.matrix.android.sdk.internal.crypto.model.CryptoDeviceInfo -import org.matrix.android.sdk.internal.crypto.model.MXUsersDevicesMap import org.matrix.android.sdk.internal.crypto.model.OlmInboundGroupSessionWrapper2 import org.matrix.android.sdk.internal.crypto.model.OlmSessionWrapper import org.matrix.android.sdk.internal.crypto.model.OutboundGroupSessionWrapper -import org.matrix.android.sdk.internal.crypto.model.event.RoomKeyWithHeldContent -import org.matrix.android.sdk.internal.crypto.model.rest.DeviceInfo -import org.matrix.android.sdk.internal.crypto.model.rest.RoomKeyRequestBody import org.matrix.android.sdk.internal.crypto.store.IMXCryptoStore -import org.matrix.android.sdk.internal.crypto.store.PrivateKeysInfo -import org.matrix.android.sdk.internal.crypto.store.SavedKeyBackupKeyInfo import org.matrix.android.sdk.internal.crypto.store.db.mapper.CrossSigningKeysMapper import org.matrix.android.sdk.internal.crypto.store.db.model.CrossSigningInfoEntity import org.matrix.android.sdk.internal.crypto.store.db.model.CrossSigningInfoEntityFields diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/db/mapper/CrossSigningKeysMapper.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/db/mapper/CrossSigningKeysMapper.kt index c15414a8dd..5e4b9b96da 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/db/mapper/CrossSigningKeysMapper.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/db/mapper/CrossSigningKeysMapper.kt @@ -19,8 +19,8 @@ package org.matrix.android.sdk.internal.crypto.store.db.mapper import com.squareup.moshi.Moshi import com.squareup.moshi.Types import io.realm.RealmList -import org.matrix.android.sdk.internal.crypto.crosssigning.DeviceTrustLevel -import org.matrix.android.sdk.internal.crypto.model.CryptoCrossSigningKey +import org.matrix.android.sdk.api.session.crypto.crosssigning.CryptoCrossSigningKey +import org.matrix.android.sdk.api.session.crypto.crosssigning.DeviceTrustLevel import org.matrix.android.sdk.internal.crypto.store.db.model.KeyInfoEntity import timber.log.Timber import javax.inject.Inject diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/db/migration/MigrateCryptoTo001Legacy.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/db/migration/MigrateCryptoTo001Legacy.kt index 0e44689428..7dee42e51a 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/db/migration/MigrateCryptoTo001Legacy.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/db/migration/MigrateCryptoTo001Legacy.kt @@ -21,7 +21,7 @@ import org.matrix.android.sdk.internal.crypto.store.db.model.OlmSessionEntityFie import org.matrix.android.sdk.internal.util.database.RealmMigrator import timber.log.Timber -class MigrateCryptoTo001Legacy(realm: DynamicRealm) : RealmMigrator(realm, 1) { +internal class MigrateCryptoTo001Legacy(realm: DynamicRealm) : RealmMigrator(realm, 1) { override fun doMigrate(realm: DynamicRealm) { Timber.d("Add field lastReceivedMessageTs (Long) and set the value to 0") diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/db/migration/MigrateCryptoTo002Legacy.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/db/migration/MigrateCryptoTo002Legacy.kt index 84e627a688..1b53e1928a 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/db/migration/MigrateCryptoTo002Legacy.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/db/migration/MigrateCryptoTo002Legacy.kt @@ -22,7 +22,7 @@ import org.matrix.android.sdk.internal.crypto.store.db.model.KeysBackupDataEntit import org.matrix.android.sdk.internal.util.database.RealmMigrator import timber.log.Timber -class MigrateCryptoTo002Legacy(realm: DynamicRealm) : RealmMigrator(realm, 2) { +internal class MigrateCryptoTo002Legacy(realm: DynamicRealm) : RealmMigrator(realm, 2) { override fun doMigrate(realm: DynamicRealm) { Timber.d("Update IncomingRoomKeyRequestEntity format: requestBodyString field is exploded into several fields") diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/db/migration/MigrateCryptoTo003RiotX.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/db/migration/MigrateCryptoTo003RiotX.kt index b468a56af6..34d1afa2d8 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/db/migration/MigrateCryptoTo003RiotX.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/db/migration/MigrateCryptoTo003RiotX.kt @@ -26,7 +26,7 @@ import org.matrix.androidsdk.crypto.data.MXDeviceInfo import org.matrix.androidsdk.crypto.data.MXOlmInboundGroupSession2 import timber.log.Timber -class MigrateCryptoTo003RiotX(realm: DynamicRealm) : RealmMigrator(realm, 3) { +internal class MigrateCryptoTo003RiotX(realm: DynamicRealm) : RealmMigrator(realm, 3) { override fun doMigrate(realm: DynamicRealm) { Timber.d("Migrate to RiotX model") @@ -40,7 +40,7 @@ class MigrateCryptoTo003RiotX(realm: DynamicRealm) : RealmMigrator(realm, 3) { try { val oldSerializedData = obj.getString("deviceInfoData") deserializeFromRealm(oldSerializedData)?.let { legacyMxDeviceInfo -> - val newMxDeviceInfo = org.matrix.android.sdk.internal.crypto.model.MXDeviceInfo( + val newMxDeviceInfo = org.matrix.android.sdk.api.session.crypto.model.MXDeviceInfo( deviceId = legacyMxDeviceInfo.deviceId, userId = legacyMxDeviceInfo.userId, algorithms = legacyMxDeviceInfo.algorithms, 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 20a4814b8d..52d0124c2b 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 @@ -19,8 +19,8 @@ package org.matrix.android.sdk.internal.crypto.store.db.migration import com.squareup.moshi.Moshi import com.squareup.moshi.Types import io.realm.DynamicRealm +import org.matrix.android.sdk.api.session.crypto.model.MXDeviceInfo import org.matrix.android.sdk.api.util.JsonDict -import org.matrix.android.sdk.internal.crypto.model.MXDeviceInfo import org.matrix.android.sdk.internal.crypto.store.db.deserializeFromRealm import org.matrix.android.sdk.internal.crypto.store.db.model.CrossSigningInfoEntityFields import org.matrix.android.sdk.internal.crypto.store.db.model.CryptoMetadataEntityFields @@ -33,7 +33,7 @@ import org.matrix.android.sdk.internal.util.database.RealmMigrator import timber.log.Timber // Version 4L added Cross Signing info persistence -class MigrateCryptoTo004(realm: DynamicRealm) : RealmMigrator(realm, 4) { +internal class MigrateCryptoTo004(realm: DynamicRealm) : RealmMigrator(realm, 4) { override fun doMigrate(realm: DynamicRealm) { if (realm.schema.contains("TrustLevelEntity")) { diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/db/migration/MigrateCryptoTo005.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/db/migration/MigrateCryptoTo005.kt index 8365d34464..e1d7598767 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/db/migration/MigrateCryptoTo005.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/db/migration/MigrateCryptoTo005.kt @@ -22,7 +22,7 @@ import org.matrix.android.sdk.internal.crypto.store.db.model.IncomingGossipingRe import org.matrix.android.sdk.internal.crypto.store.db.model.OutgoingGossipingRequestEntityFields import org.matrix.android.sdk.internal.util.database.RealmMigrator -class MigrateCryptoTo005(realm: DynamicRealm) : RealmMigrator(realm, 5) { +internal class MigrateCryptoTo005(realm: DynamicRealm) : RealmMigrator(realm, 5) { override fun doMigrate(realm: DynamicRealm) { realm.schema.remove("OutgoingRoomKeyRequestEntity") diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/db/migration/MigrateCryptoTo006.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/db/migration/MigrateCryptoTo006.kt index a29a791826..39b2898514 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/db/migration/MigrateCryptoTo006.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/db/migration/MigrateCryptoTo006.kt @@ -21,7 +21,7 @@ import org.matrix.android.sdk.internal.crypto.store.db.model.CryptoMetadataEntit import org.matrix.android.sdk.internal.util.database.RealmMigrator import timber.log.Timber -class MigrateCryptoTo006(realm: DynamicRealm) : RealmMigrator(realm, 6) { +internal class MigrateCryptoTo006(realm: DynamicRealm) : RealmMigrator(realm, 6) { override fun doMigrate(realm: DynamicRealm) { Timber.d("Updating CryptoMetadataEntity table") diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/db/migration/MigrateCryptoTo007.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/db/migration/MigrateCryptoTo007.kt index 7ae58e7fc0..718b9787d2 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/db/migration/MigrateCryptoTo007.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/db/migration/MigrateCryptoTo007.kt @@ -28,7 +28,7 @@ import org.matrix.android.sdk.internal.di.MoshiProvider import org.matrix.android.sdk.internal.util.database.RealmMigrator import timber.log.Timber -class MigrateCryptoTo007(realm: DynamicRealm) : RealmMigrator(realm, 7) { +internal class MigrateCryptoTo007(realm: DynamicRealm) : RealmMigrator(realm, 7) { override fun doMigrate(realm: DynamicRealm) { Timber.d("Updating KeyInfoEntity table") diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/db/migration/MigrateCryptoTo008.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/db/migration/MigrateCryptoTo008.kt index e3bd3f035a..785e6a04f4 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/db/migration/MigrateCryptoTo008.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/db/migration/MigrateCryptoTo008.kt @@ -22,7 +22,7 @@ import org.matrix.android.sdk.internal.crypto.store.db.model.DeviceInfoEntityFie import org.matrix.android.sdk.internal.crypto.store.db.model.MyDeviceLastSeenInfoEntityFields import org.matrix.android.sdk.internal.util.database.RealmMigrator -class MigrateCryptoTo008(realm: DynamicRealm) : RealmMigrator(realm, 8) { +internal class MigrateCryptoTo008(realm: DynamicRealm) : RealmMigrator(realm, 8) { override fun doMigrate(realm: DynamicRealm) { realm.schema.create("MyDeviceLastSeenInfoEntity") diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/db/migration/MigrateCryptoTo009.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/db/migration/MigrateCryptoTo009.kt index ed705318f9..8d9d24dfba 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/db/migration/MigrateCryptoTo009.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/db/migration/MigrateCryptoTo009.kt @@ -23,7 +23,7 @@ import org.matrix.android.sdk.internal.util.database.RealmMigrator import timber.log.Timber // Fixes duplicate devices in UserEntity#devices -class MigrateCryptoTo009(realm: DynamicRealm) : RealmMigrator(realm, 9) { +internal class MigrateCryptoTo009(realm: DynamicRealm) : RealmMigrator(realm, 9) { override fun doMigrate(realm: DynamicRealm) { val userEntities = realm.where("UserEntity").findAll() diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/db/migration/MigrateCryptoTo010.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/db/migration/MigrateCryptoTo010.kt index 8d69ee5558..faf0d58832 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/db/migration/MigrateCryptoTo010.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/db/migration/MigrateCryptoTo010.kt @@ -22,7 +22,7 @@ import org.matrix.android.sdk.internal.crypto.store.db.model.WithHeldSessionEnti import org.matrix.android.sdk.internal.util.database.RealmMigrator // Version 10L added WithHeld Keys Info (MSC2399) -class MigrateCryptoTo010(realm: DynamicRealm) : RealmMigrator(realm, 10) { +internal class MigrateCryptoTo010(realm: DynamicRealm) : RealmMigrator(realm, 10) { override fun doMigrate(realm: DynamicRealm) { realm.schema.create("WithHeldSessionEntity") diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/db/migration/MigrateCryptoTo011.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/db/migration/MigrateCryptoTo011.kt index c9825a7f3d..feaab4bb19 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/db/migration/MigrateCryptoTo011.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/db/migration/MigrateCryptoTo011.kt @@ -21,7 +21,7 @@ import org.matrix.android.sdk.internal.crypto.store.db.model.CryptoMetadataEntit import org.matrix.android.sdk.internal.util.database.RealmMigrator // Version 11L added deviceKeysSentToServer boolean to CryptoMetadataEntity -class MigrateCryptoTo011(realm: DynamicRealm) : RealmMigrator(realm, 11) { +internal class MigrateCryptoTo011(realm: DynamicRealm) : RealmMigrator(realm, 11) { override fun doMigrate(realm: DynamicRealm) { realm.schema.get("CryptoMetadataEntity") diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/db/migration/MigrateCryptoTo012.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/db/migration/MigrateCryptoTo012.kt index 6b1460d9d6..4626757a06 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/db/migration/MigrateCryptoTo012.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/db/migration/MigrateCryptoTo012.kt @@ -22,7 +22,7 @@ import org.matrix.android.sdk.internal.crypto.store.db.model.OutboundGroupSessio import org.matrix.android.sdk.internal.util.database.RealmMigrator // Version 12L added outbound group session persistence -class MigrateCryptoTo012(realm: DynamicRealm) : RealmMigrator(realm, 12) { +internal class MigrateCryptoTo012(realm: DynamicRealm) : RealmMigrator(realm, 12) { override fun doMigrate(realm: DynamicRealm) { val outboundEntitySchema = realm.schema.create("OutboundGroupSessionInfoEntity") diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/db/migration/MigrateCryptoTo013.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/db/migration/MigrateCryptoTo013.kt index dc22c5f133..dc8984da41 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/db/migration/MigrateCryptoTo013.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/db/migration/MigrateCryptoTo013.kt @@ -21,7 +21,7 @@ import org.matrix.android.sdk.internal.util.database.RealmMigrator import timber.log.Timber // Version 13L delete unreferenced TrustLevelEntity -class MigrateCryptoTo013(realm: DynamicRealm) : RealmMigrator(realm, 13) { +internal class MigrateCryptoTo013(realm: DynamicRealm) : RealmMigrator(realm, 13) { override fun doMigrate(realm: DynamicRealm) { // Use a trick to do that... Ref: https://stackoverflow.com/questions/55221366 diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/db/migration/MigrateCryptoTo014.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/db/migration/MigrateCryptoTo014.kt index f0089e3427..548672790a 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/db/migration/MigrateCryptoTo014.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/db/migration/MigrateCryptoTo014.kt @@ -22,7 +22,7 @@ import org.matrix.android.sdk.internal.crypto.store.db.model.SharedSessionEntity import org.matrix.android.sdk.internal.util.database.RealmMigrator // Version 14L Update the way we remember key sharing -class MigrateCryptoTo014(realm: DynamicRealm) : RealmMigrator(realm, 14) { +internal class MigrateCryptoTo014(realm: DynamicRealm) : RealmMigrator(realm, 14) { override fun doMigrate(realm: DynamicRealm) { realm.schema.get("SharedSessionEntity") diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/db/migration/MigrateCryptoTo015.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/db/migration/MigrateCryptoTo015.kt index 465c18555a..bca02c2e6e 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/db/migration/MigrateCryptoTo015.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/db/migration/MigrateCryptoTo015.kt @@ -17,12 +17,12 @@ package org.matrix.android.sdk.internal.crypto.store.db.migration import io.realm.DynamicRealm -import org.matrix.android.sdk.internal.crypto.MXCRYPTO_ALGORITHM_MEGOLM +import org.matrix.android.sdk.api.crypto.MXCRYPTO_ALGORITHM_MEGOLM import org.matrix.android.sdk.internal.crypto.store.db.model.CryptoRoomEntityFields import org.matrix.android.sdk.internal.util.database.RealmMigrator // Version 15L adds wasEncryptedOnce field to CryptoRoomEntity -class MigrateCryptoTo015(realm: DynamicRealm) : RealmMigrator(realm, 15) { +internal class MigrateCryptoTo015(realm: DynamicRealm) : RealmMigrator(realm, 15) { override fun doMigrate(realm: DynamicRealm) { realm.schema.get("CryptoRoomEntity") diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/db/model/CrossSigningInfoEntity.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/db/model/CrossSigningInfoEntity.kt index 8599c972e9..5aba9bb9ba 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/db/model/CrossSigningInfoEntity.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/db/model/CrossSigningInfoEntity.kt @@ -19,7 +19,7 @@ package org.matrix.android.sdk.internal.crypto.store.db.model import io.realm.RealmList import io.realm.RealmObject import io.realm.annotations.PrimaryKey -import org.matrix.android.sdk.internal.crypto.model.KeyUsage +import org.matrix.android.sdk.api.session.crypto.crosssigning.KeyUsage import org.matrix.android.sdk.internal.extensions.clearWith internal open class CrossSigningInfoEntity( diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/db/model/CryptoMapper.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/db/model/CryptoMapper.kt index 7ba986699a..c71d5e73a2 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/db/model/CryptoMapper.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/db/model/CryptoMapper.kt @@ -17,14 +17,14 @@ package org.matrix.android.sdk.internal.crypto.store.db.model import com.squareup.moshi.Moshi import com.squareup.moshi.Types +import org.matrix.android.sdk.api.session.crypto.crosssigning.DeviceTrustLevel +import org.matrix.android.sdk.api.session.crypto.model.CryptoDeviceInfo +import org.matrix.android.sdk.api.session.crypto.model.UnsignedDeviceInfo import org.matrix.android.sdk.api.util.JsonDict -import org.matrix.android.sdk.internal.crypto.crosssigning.DeviceTrustLevel -import org.matrix.android.sdk.internal.crypto.model.CryptoDeviceInfo -import org.matrix.android.sdk.internal.crypto.model.rest.UnsignedDeviceInfo import org.matrix.android.sdk.internal.di.SerializeNulls import timber.log.Timber -object CryptoMapper { +internal object CryptoMapper { private val moshi = Moshi.Builder().add(SerializeNulls.JSON_ADAPTER_FACTORY).build() private val listMigrationAdapter = moshi.adapter>(Types.newParameterizedType( diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/db/model/CryptoRoomEntity.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/db/model/CryptoRoomEntity.kt index 6167314b5a..114a596964 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/db/model/CryptoRoomEntity.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/db/model/CryptoRoomEntity.kt @@ -31,8 +31,8 @@ internal open class CryptoRoomEntity( // a security to ensure that a room will never revert to not encrypted // even if a new state event with empty encryption, or state is reset somehow var wasEncryptedOnce: Boolean? = false - ) : - RealmObject() { +) : + RealmObject() { companion object } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/db/model/GossipingEventEntity.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/db/model/GossipingEventEntity.kt index 75094f01db..a024e092b7 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/db/model/GossipingEventEntity.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/db/model/GossipingEventEntity.kt @@ -20,10 +20,10 @@ import com.squareup.moshi.JsonDataException import io.realm.RealmObject import io.realm.annotations.Index import org.matrix.android.sdk.api.session.crypto.MXCryptoError +import org.matrix.android.sdk.api.session.crypto.model.MXEventDecryptionResult +import org.matrix.android.sdk.api.session.crypto.model.OlmDecryptionResult import org.matrix.android.sdk.api.session.events.model.Event import org.matrix.android.sdk.api.session.room.send.SendState -import org.matrix.android.sdk.internal.crypto.MXEventDecryptionResult -import org.matrix.android.sdk.internal.crypto.algorithms.olm.OlmDecryptionResult import org.matrix.android.sdk.internal.database.mapper.ContentMapper import org.matrix.android.sdk.internal.di.MoshiProvider import timber.log.Timber diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/db/model/IncomingGossipingRequestEntity.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/db/model/IncomingGossipingRequestEntity.kt index 4457a44cb2..f05c8853c8 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/db/model/IncomingGossipingRequestEntity.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/db/model/IncomingGossipingRequestEntity.kt @@ -19,12 +19,12 @@ package org.matrix.android.sdk.internal.crypto.store.db.model import io.realm.RealmObject import io.realm.annotations.Index import org.matrix.android.sdk.api.extensions.tryOrNull +import org.matrix.android.sdk.api.session.crypto.model.GossipingRequestState +import org.matrix.android.sdk.api.session.crypto.model.IncomingRoomKeyRequest +import org.matrix.android.sdk.api.session.crypto.model.IncomingSecretShareRequest +import org.matrix.android.sdk.api.session.crypto.model.RoomKeyRequestBody import org.matrix.android.sdk.internal.crypto.GossipRequestType -import org.matrix.android.sdk.internal.crypto.GossipingRequestState -import org.matrix.android.sdk.internal.crypto.IncomingRoomKeyRequest -import org.matrix.android.sdk.internal.crypto.IncomingSecretShareRequest import org.matrix.android.sdk.internal.crypto.IncomingShareRequestCommon -import org.matrix.android.sdk.internal.crypto.model.rest.RoomKeyRequestBody internal open class IncomingGossipingRequestEntity(@Index var requestId: String? = "", @Index var typeStr: String? = null, diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/db/model/OlmInboundGroupSessionEntity.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/db/model/OlmInboundGroupSessionEntity.kt index f330e8822a..83671b28d9 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/db/model/OlmInboundGroupSessionEntity.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/db/model/OlmInboundGroupSessionEntity.kt @@ -34,7 +34,7 @@ internal open class OlmInboundGroupSessionEntity( var olmInboundGroupSessionData: String? = null, // Indicate if the key has been backed up to the homeserver var backedUp: Boolean = false) : - RealmObject() { + RealmObject() { fun getInboundGroupSession(): OlmInboundGroupSessionWrapper2? { return try { diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/db/model/OlmSessionEntity.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/db/model/OlmSessionEntity.kt index 0b69311c57..1a637d76c4 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/db/model/OlmSessionEntity.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/db/model/OlmSessionEntity.kt @@ -30,7 +30,7 @@ internal open class OlmSessionEntity(@PrimaryKey var primaryKey: String = "", var deviceKey: String? = null, var olmSessionData: String? = null, var lastReceivedMessageTs: Long = 0) : - RealmObject() { + RealmObject() { fun getOlmSession(): OlmSession? { return deserializeFromRealm(olmSessionData) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/db/model/OutgoingGossipingRequestEntity.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/db/model/OutgoingGossipingRequestEntity.kt index a19547fddc..0e1278967e 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/db/model/OutgoingGossipingRequestEntity.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/db/model/OutgoingGossipingRequestEntity.kt @@ -21,12 +21,12 @@ import com.squareup.moshi.Types import io.realm.RealmObject import io.realm.annotations.Index import org.matrix.android.sdk.api.extensions.tryOrNull +import org.matrix.android.sdk.api.session.crypto.model.OutgoingGossipingRequestState +import org.matrix.android.sdk.api.session.crypto.model.OutgoingRoomKeyRequest +import org.matrix.android.sdk.api.session.crypto.model.RoomKeyRequestBody import org.matrix.android.sdk.internal.crypto.GossipRequestType import org.matrix.android.sdk.internal.crypto.OutgoingGossipingRequest -import org.matrix.android.sdk.internal.crypto.OutgoingGossipingRequestState -import org.matrix.android.sdk.internal.crypto.OutgoingRoomKeyRequest import org.matrix.android.sdk.internal.crypto.OutgoingSecretRequest -import org.matrix.android.sdk.internal.crypto.model.rest.RoomKeyRequestBody import org.matrix.android.sdk.internal.di.MoshiProvider internal open class OutgoingGossipingRequestEntity( diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/db/model/WithHeldSessionEntity.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/db/model/WithHeldSessionEntity.kt index 6d7889053b..93048e5775 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/db/model/WithHeldSessionEntity.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/db/model/WithHeldSessionEntity.kt @@ -18,7 +18,7 @@ package org.matrix.android.sdk.internal.crypto.store.db.model import io.realm.RealmObject import io.realm.annotations.Index -import org.matrix.android.sdk.internal.crypto.model.event.WithHeldCode +import org.matrix.android.sdk.api.session.events.model.content.WithHeldCode /** * When an encrypted message is sent in a room, the megolm key might not be sent to all devices present in the room. 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 2784e58425..8bf9794375 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 @@ -20,7 +20,7 @@ import io.realm.Realm import io.realm.RealmResults import io.realm.kotlin.createObject import io.realm.kotlin.where -import org.matrix.android.sdk.internal.crypto.MXCRYPTO_ALGORITHM_MEGOLM +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 diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/db/query/WithHeldSessionQueries.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/db/query/WithHeldSessionQueries.kt index b8a3e36137..c253af2bf6 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/db/query/WithHeldSessionQueries.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/db/query/WithHeldSessionQueries.kt @@ -19,7 +19,7 @@ package org.matrix.android.sdk.internal.crypto.store.db.query import io.realm.Realm import io.realm.kotlin.createObject import io.realm.kotlin.where -import org.matrix.android.sdk.internal.crypto.MXCRYPTO_ALGORITHM_MEGOLM +import org.matrix.android.sdk.api.crypto.MXCRYPTO_ALGORITHM_MEGOLM import org.matrix.android.sdk.internal.crypto.store.db.model.WithHeldSessionEntity import org.matrix.android.sdk.internal.crypto.store.db.model.WithHeldSessionEntityFields diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/tasks/ClaimOneTimeKeysForUsersDeviceTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/tasks/ClaimOneTimeKeysForUsersDeviceTask.kt index d5cf749db7..96848a264d 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/tasks/ClaimOneTimeKeysForUsersDeviceTask.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/tasks/ClaimOneTimeKeysForUsersDeviceTask.kt @@ -16,9 +16,9 @@ package org.matrix.android.sdk.internal.crypto.tasks +import org.matrix.android.sdk.api.session.crypto.model.MXUsersDevicesMap import org.matrix.android.sdk.internal.crypto.api.CryptoApi import org.matrix.android.sdk.internal.crypto.model.MXKey -import org.matrix.android.sdk.internal.crypto.model.MXUsersDevicesMap import org.matrix.android.sdk.internal.crypto.model.rest.KeysClaimBody import org.matrix.android.sdk.internal.network.GlobalErrorReceiver import org.matrix.android.sdk.internal.network.executeRequest 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 627352f568..1e395796a9 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/tasks/EncryptEventTask.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/tasks/EncryptEventTask.kt @@ -15,18 +15,18 @@ */ package org.matrix.android.sdk.internal.crypto.tasks +import org.matrix.android.sdk.api.crypto.MXCRYPTO_ALGORITHM_MEGOLM import org.matrix.android.sdk.api.session.crypto.CryptoService +import org.matrix.android.sdk.api.session.crypto.model.MXEncryptEventContentResult +import org.matrix.android.sdk.api.session.crypto.model.MXEventDecryptionResult import org.matrix.android.sdk.api.session.events.model.Event import org.matrix.android.sdk.api.session.events.model.EventType import org.matrix.android.sdk.api.session.events.model.toContent import org.matrix.android.sdk.api.session.room.send.SendState -import org.matrix.android.sdk.internal.crypto.MXCRYPTO_ALGORITHM_MEGOLM -import org.matrix.android.sdk.internal.crypto.MXEventDecryptionResult -import org.matrix.android.sdk.internal.crypto.model.MXEncryptEventContentResult +import org.matrix.android.sdk.api.util.awaitCallback import org.matrix.android.sdk.internal.database.mapper.ContentMapper import org.matrix.android.sdk.internal.session.room.send.LocalEchoRepository import org.matrix.android.sdk.internal.task.Task -import org.matrix.android.sdk.internal.util.awaitCallback import javax.inject.Inject internal interface EncryptEventTask : Task { diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/tasks/GetDeviceInfoTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/tasks/GetDeviceInfoTask.kt index 9f20ea598d..87dbd8d1a0 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/tasks/GetDeviceInfoTask.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/tasks/GetDeviceInfoTask.kt @@ -16,8 +16,8 @@ package org.matrix.android.sdk.internal.crypto.tasks +import org.matrix.android.sdk.api.session.crypto.model.DeviceInfo import org.matrix.android.sdk.internal.crypto.api.CryptoApi -import org.matrix.android.sdk.internal.crypto.model.rest.DeviceInfo import org.matrix.android.sdk.internal.network.GlobalErrorReceiver import org.matrix.android.sdk.internal.network.executeRequest import org.matrix.android.sdk.internal.task.Task diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/tasks/GetDevicesTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/tasks/GetDevicesTask.kt index 52f9f73299..27cb17f2ab 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/tasks/GetDevicesTask.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/tasks/GetDevicesTask.kt @@ -16,8 +16,8 @@ package org.matrix.android.sdk.internal.crypto.tasks +import org.matrix.android.sdk.api.session.crypto.model.DevicesListResponse import org.matrix.android.sdk.internal.crypto.api.CryptoApi -import org.matrix.android.sdk.internal.crypto.model.rest.DevicesListResponse import org.matrix.android.sdk.internal.network.GlobalErrorReceiver import org.matrix.android.sdk.internal.network.executeRequest import org.matrix.android.sdk.internal.task.Task diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/tasks/InitializeCrossSigningTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/tasks/InitializeCrossSigningTask.kt index e2fd54f0d8..eefdd25044 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/tasks/InitializeCrossSigningTask.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/tasks/InitializeCrossSigningTask.kt @@ -18,13 +18,13 @@ package org.matrix.android.sdk.internal.crypto.tasks import dagger.Lazy import org.matrix.android.sdk.api.auth.UserInteractiveAuthInterceptor +import org.matrix.android.sdk.api.session.crypto.crosssigning.CryptoCrossSigningKey +import org.matrix.android.sdk.api.session.crypto.crosssigning.KeyUsage +import org.matrix.android.sdk.api.util.toBase64NoPadding import org.matrix.android.sdk.internal.auth.registration.handleUIA import org.matrix.android.sdk.internal.crypto.MXOlmDevice import org.matrix.android.sdk.internal.crypto.MyDeviceInfoHolder import org.matrix.android.sdk.internal.crypto.crosssigning.canonicalSignable -import org.matrix.android.sdk.internal.crypto.crosssigning.toBase64NoPadding -import org.matrix.android.sdk.internal.crypto.model.CryptoCrossSigningKey -import org.matrix.android.sdk.internal.crypto.model.KeyUsage import org.matrix.android.sdk.internal.crypto.model.rest.UploadSignatureQueryBuilder import org.matrix.android.sdk.internal.di.UserId import org.matrix.android.sdk.internal.task.Task diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/tasks/SendToDeviceTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/tasks/SendToDeviceTask.kt index c6af9b0cd1..fc4d422360 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/tasks/SendToDeviceTask.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/tasks/SendToDeviceTask.kt @@ -16,8 +16,8 @@ package org.matrix.android.sdk.internal.crypto.tasks +import org.matrix.android.sdk.api.session.crypto.model.MXUsersDevicesMap import org.matrix.android.sdk.internal.crypto.api.CryptoApi -import org.matrix.android.sdk.internal.crypto.model.MXUsersDevicesMap import org.matrix.android.sdk.internal.crypto.model.rest.SendToDeviceBody import org.matrix.android.sdk.internal.network.GlobalErrorReceiver import org.matrix.android.sdk.internal.network.executeRequest diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/tasks/UploadSigningKeysTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/tasks/UploadSigningKeysTask.kt index 6cb14ded63..0a0df11bd3 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/tasks/UploadSigningKeysTask.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/tasks/UploadSigningKeysTask.kt @@ -18,10 +18,10 @@ package org.matrix.android.sdk.internal.crypto.tasks import org.matrix.android.sdk.api.auth.UIABaseAuth import org.matrix.android.sdk.api.failure.Failure +import org.matrix.android.sdk.api.session.crypto.crosssigning.CryptoCrossSigningKey +import org.matrix.android.sdk.api.session.crypto.crosssigning.toRest import org.matrix.android.sdk.internal.crypto.api.CryptoApi -import org.matrix.android.sdk.internal.crypto.model.CryptoCrossSigningKey import org.matrix.android.sdk.internal.crypto.model.rest.UploadSigningKeysBody -import org.matrix.android.sdk.internal.crypto.model.toRest import org.matrix.android.sdk.internal.network.GlobalErrorReceiver import org.matrix.android.sdk.internal.network.executeRequest import org.matrix.android.sdk.internal.task.Task @@ -42,7 +42,7 @@ internal interface UploadSigningKeysTask : Task?) : Failure.FeatureFailure() +internal data class UploadSigningKeys(val failures: Map?) : Failure.FeatureFailure() internal class DefaultUploadSigningKeysTask @Inject constructor( private val cryptoApi: CryptoApi, diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/tools/HkdfSha256.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/tools/HkdfSha256.kt index 6839ccd326..b230f0d029 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/tools/HkdfSha256.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/tools/HkdfSha256.kt @@ -26,7 +26,7 @@ import kotlin.math.ceil * HMAC-based Extract-and-Expand Key Derivation Function (HkdfSha256) * [RFC-5869] https://tools.ietf.org/html/rfc5869 */ -object HkdfSha256 { +internal object HkdfSha256 { fun deriveSecret(inputKeyMaterial: ByteArray, salt: ByteArray?, info: ByteArray, outputLength: Int): ByteArray { return expand(extract(salt, inputKeyMaterial), info, outputLength) @@ -70,7 +70,7 @@ object HkdfSha256 { T(2) = HMAC-Hash(PRK, T(1) | info | 0x02) T(3) = HMAC-Hash(PRK, T(2) | info | 0x03) ... - */ + */ val n = ceil(outputLength.toDouble() / HASH_LEN.toDouble()).toInt() var stepHash = ByteArray(0) // T(0) empty string (zero length) 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 388ecb9659..28bf1d70f7 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 @@ -23,10 +23,13 @@ import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.launch import org.matrix.android.sdk.api.MatrixCoroutineDispatchers import org.matrix.android.sdk.api.session.crypto.crosssigning.CrossSigningService +import org.matrix.android.sdk.api.session.crypto.crosssigning.DeviceTrustLevel import org.matrix.android.sdk.api.session.crypto.crosssigning.KEYBACKUP_SECRET_SSSS_NAME import org.matrix.android.sdk.api.session.crypto.crosssigning.MASTER_KEY_SSSS_NAME import org.matrix.android.sdk.api.session.crypto.crosssigning.SELF_SIGNING_KEY_SSSS_NAME import org.matrix.android.sdk.api.session.crypto.crosssigning.USER_SIGNING_KEY_SSSS_NAME +import org.matrix.android.sdk.api.session.crypto.model.CryptoDeviceInfo +import org.matrix.android.sdk.api.session.crypto.model.MXUsersDevicesMap 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.QrCodeVerificationTransaction @@ -41,6 +44,7 @@ import org.matrix.android.sdk.api.session.events.model.Event import org.matrix.android.sdk.api.session.events.model.EventType import org.matrix.android.sdk.api.session.events.model.LocalEcho import org.matrix.android.sdk.api.session.events.model.RelationType +import org.matrix.android.sdk.api.session.events.model.content.EncryptedEventContent import org.matrix.android.sdk.api.session.events.model.toModel import org.matrix.android.sdk.api.session.room.model.message.MessageContent import org.matrix.android.sdk.api.session.room.model.message.MessageRelationContent @@ -59,10 +63,6 @@ import org.matrix.android.sdk.internal.crypto.IncomingGossipingRequestManager import org.matrix.android.sdk.internal.crypto.MyDeviceInfoHolder import org.matrix.android.sdk.internal.crypto.OutgoingGossipingRequestManager import org.matrix.android.sdk.internal.crypto.actions.SetDeviceVerificationAction -import org.matrix.android.sdk.internal.crypto.crosssigning.DeviceTrustLevel -import org.matrix.android.sdk.internal.crypto.model.CryptoDeviceInfo -import org.matrix.android.sdk.internal.crypto.model.MXUsersDevicesMap -import org.matrix.android.sdk.internal.crypto.model.event.EncryptedEventContent import org.matrix.android.sdk.internal.crypto.model.rest.KeyVerificationAccept import org.matrix.android.sdk.internal.crypto.model.rest.KeyVerificationCancel import org.matrix.android.sdk.internal.crypto.model.rest.KeyVerificationDone @@ -364,14 +364,14 @@ internal class DefaultVerificationService @Inject constructor( dispatchRequestAdded(pendingVerificationRequest) /* - * After the m.key.verification.ready event is sent, either party can send an m.key.verification.start event - * to begin the verification. - * If both parties send an m.key.verification.start event, and they both specify the same verification method, - * then the event sent by the user whose user ID is the smallest is used, and the other m.key.verification.start - * event is ignored. - * In the case of a single user verifying two of their devices, the device ID is compared instead. - * If both parties send an m.key.verification.start event, but they specify different verification methods, - * the verification should be cancelled with a code of m.unexpected_message. + * After the m.key.verification.ready event is sent, either party can send an m.key.verification.start event + * to begin the verification. + * If both parties send an m.key.verification.start event, and they both specify the same verification method, + * then the event sent by the user whose user ID is the smallest is used, and the other m.key.verification.start + * event is ignored. + * In the case of a single user verifying two of their devices, the device ID is compared instead. + * If both parties send an m.key.verification.start event, but they specify different verification methods, + * the verification should be cancelled with a code of m.unexpected_message. */ } 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 6043c21b66..69d9388c5f 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 @@ -17,12 +17,12 @@ package org.matrix.android.sdk.internal.crypto.verification import org.matrix.android.sdk.api.MatrixCallback import org.matrix.android.sdk.api.session.crypto.crosssigning.CrossSigningService +import org.matrix.android.sdk.api.session.crypto.crosssigning.DeviceTrustLevel import org.matrix.android.sdk.api.session.crypto.verification.VerificationTransaction import org.matrix.android.sdk.api.session.crypto.verification.VerificationTxState import org.matrix.android.sdk.internal.crypto.IncomingGossipingRequestManager import org.matrix.android.sdk.internal.crypto.OutgoingGossipingRequestManager import org.matrix.android.sdk.internal.crypto.actions.SetDeviceVerificationAction -import org.matrix.android.sdk.internal.crypto.crosssigning.DeviceTrustLevel import timber.log.Timber /** diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/verification/SendVerificationMessageWorker.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/verification/SendVerificationMessageWorker.kt index a763c05e07..0a175ae3ca 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/verification/SendVerificationMessageWorker.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/verification/SendVerificationMessageWorker.kt @@ -35,7 +35,7 @@ import javax.inject.Inject * Possible next worker : None */ internal class SendVerificationMessageWorker(context: Context, params: WorkerParameters, sessionManager: SessionManager) : - SessionSafeCoroutineWorker(context, params, sessionManager, Params::class.java) { + SessionSafeCoroutineWorker(context, params, sessionManager, Params::class.java) { @JsonClass(generateAdapter = true) internal data class Params( diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/verification/VerificationInfo.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/verification/VerificationInfo.kt index 368a9b6b54..0615773a7b 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/verification/VerificationInfo.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/verification/VerificationInfo.kt @@ -15,10 +15,10 @@ */ package org.matrix.android.sdk.internal.crypto.verification +import org.matrix.android.sdk.api.session.crypto.model.SendToDeviceObject import org.matrix.android.sdk.api.session.events.model.Content -import org.matrix.android.sdk.internal.crypto.model.rest.SendToDeviceObject -interface VerificationInfo { +internal interface VerificationInfo { fun toEventContent(): Content? = null fun toSendToDeviceObject(): SendToDeviceObject? = null 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 f727aff39d..40c96dfa95 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 @@ -103,7 +103,7 @@ internal interface VerificationInfoStart : VerificationInfo): VerificationInfoAccept = MessageVerificationAcceptContent.create( - tid, - keyAgreementProtocol, - hash, - commitment, - messageAuthenticationCode, - shortAuthenticationStrings - ) + tid, + keyAgreementProtocol, + hash, + commitment, + messageAuthenticationCode, + shortAuthenticationStrings + ) override fun createKey(tid: String, pubKey: String): VerificationInfoKey = MessageVerificationKeyContent.create(tid, pubKey) 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 45f8143937..40deda2745 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 @@ -16,12 +16,12 @@ package org.matrix.android.sdk.internal.crypto.verification import org.matrix.android.sdk.api.MatrixCallback +import org.matrix.android.sdk.api.session.crypto.model.MXUsersDevicesMap import org.matrix.android.sdk.api.session.crypto.verification.CancelCode import org.matrix.android.sdk.api.session.crypto.verification.ValidVerificationInfoRequest import org.matrix.android.sdk.api.session.crypto.verification.VerificationTxState import org.matrix.android.sdk.api.session.events.model.EventType import org.matrix.android.sdk.api.session.room.model.message.MessageType -import org.matrix.android.sdk.internal.crypto.model.MXUsersDevicesMap import org.matrix.android.sdk.internal.crypto.model.rest.KeyVerificationAccept import org.matrix.android.sdk.internal.crypto.model.rest.KeyVerificationCancel import org.matrix.android.sdk.internal.crypto.model.rest.KeyVerificationDone 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 829e066bf3..06f0b36798 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 @@ -21,15 +21,14 @@ import org.matrix.android.sdk.api.session.crypto.verification.CancelCode import org.matrix.android.sdk.api.session.crypto.verification.QrCodeVerificationTransaction import org.matrix.android.sdk.api.session.crypto.verification.VerificationTxState import org.matrix.android.sdk.api.session.events.model.EventType +import org.matrix.android.sdk.api.util.fromBase64 import org.matrix.android.sdk.internal.crypto.IncomingGossipingRequestManager import org.matrix.android.sdk.internal.crypto.OutgoingGossipingRequestManager import org.matrix.android.sdk.internal.crypto.actions.SetDeviceVerificationAction -import org.matrix.android.sdk.internal.crypto.crosssigning.fromBase64 import org.matrix.android.sdk.internal.crypto.crosssigning.fromBase64Safe import org.matrix.android.sdk.internal.crypto.store.IMXCryptoStore import org.matrix.android.sdk.internal.crypto.verification.DefaultVerificationTransaction import org.matrix.android.sdk.internal.crypto.verification.ValidVerificationInfoStart -import org.matrix.android.sdk.internal.util.exhaustive import timber.log.Timber internal class DefaultQrCodeVerificationTransaction( @@ -129,7 +128,7 @@ internal class DefaultQrCodeVerificationTransaction( // Nothing special here, we will send a reciprocate start event, and then the other session will trust it's view of the MSK } } - }.exhaustive + } val toVerifyDeviceIds = mutableListOf() @@ -174,7 +173,7 @@ internal class DefaultQrCodeVerificationTransaction( Unit } } - }.exhaustive + } if (!canTrustOtherUserMasterKey && toVerifyDeviceIds.isEmpty()) { // Nothing to verify @@ -272,6 +271,7 @@ internal class DefaultQrCodeVerificationTransaction( // I now know that i can trust my MSK trust(true, emptyList(), true) } + 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 76e88442b6..b80c29c244 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 @@ -16,14 +16,14 @@ package org.matrix.android.sdk.internal.crypto.verification.qrcode -import org.matrix.android.sdk.internal.crypto.crosssigning.fromBase64 -import org.matrix.android.sdk.internal.crypto.crosssigning.toBase64NoPadding +import org.matrix.android.sdk.api.util.fromBase64 +import org.matrix.android.sdk.api.util.toBase64NoPadding import org.matrix.android.sdk.internal.extensions.toUnsignedInt // MATRIX private val prefix = "MATRIX".toByteArray(Charsets.ISO_8859_1) -fun QrCodeData.toEncodedString(): String { +internal fun QrCodeData.toEncodedString(): String { var result = ByteArray(0) // MATRIX @@ -67,7 +67,7 @@ fun QrCodeData.toEncodedString(): String { return result.toString(Charsets.ISO_8859_1) } -fun String.toQrCodeData(): QrCodeData? { +internal fun String.toQrCodeData(): QrCodeData? { val byteArray = toByteArray(Charsets.ISO_8859_1) // Size should be min 6 + 1 + 1 + 2 + ? + 32 + 32 + ? = 74 + transactionLength + secretLength diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/verification/qrcode/QrCodeData.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/verification/qrcode/QrCodeData.kt index 25c04efde7..0ac57db9bc 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/verification/qrcode/QrCodeData.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/verification/qrcode/QrCodeData.kt @@ -19,7 +19,7 @@ package org.matrix.android.sdk.internal.crypto.verification.qrcode /** * Ref: https://github.com/uhoreg/matrix-doc/blob/qr_key_verification/proposals/1543-qr_code_key_verification.md#qr-code-format */ -sealed class QrCodeData( +internal sealed class QrCodeData( /** * the event ID or transaction_id of the associated verification */ diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/verification/qrcode/SharedSecret.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/verification/qrcode/SharedSecret.kt index 858c0ab6af..52b09be49c 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/verification/qrcode/SharedSecret.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/verification/qrcode/SharedSecret.kt @@ -16,10 +16,10 @@ package org.matrix.android.sdk.internal.crypto.verification.qrcode -import org.matrix.android.sdk.internal.crypto.crosssigning.toBase64NoPadding +import org.matrix.android.sdk.api.util.toBase64NoPadding import java.security.SecureRandom -fun generateSharedSecretV2(): String { +internal fun generateSharedSecretV2(): String { val secureRandom = SecureRandom() // 8 bytes long diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/AsyncTransaction.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/AsyncTransaction.kt index ebc9bcce5a..315d77d932 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/AsyncTransaction.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/AsyncTransaction.kt @@ -35,7 +35,7 @@ internal fun CoroutineScope.asyncTransaction(realmConfiguration: RealmConfig } } -suspend fun awaitTransaction(config: RealmConfiguration, transaction: suspend (realm: Realm) -> T): T { +internal suspend fun awaitTransaction(config: RealmConfiguration, transaction: suspend (realm: Realm) -> T): T { return withContext(Realm.WRITE_EXECUTOR.asCoroutineDispatcher()) { Realm.getInstance(config).use { bgRealm -> bgRealm.beginTransaction() diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/EventInsertLiveObserver.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/EventInsertLiveObserver.kt index 115025cc7d..b057b4c319 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 @@ -32,7 +32,7 @@ import javax.inject.Inject internal class EventInsertLiveObserver @Inject constructor(@SessionDatabase realmConfiguration: RealmConfiguration, private val processors: Set<@JvmSuppressWildcards EventInsertLiveProcessor>) : - RealmLiveEntityObserver(realmConfiguration) { + RealmLiveEntityObserver(realmConfiguration) { override val query = Monarchy.Query { it.where(EventInsertEntity::class.java).equalTo(EventInsertEntityFields.CAN_BE_PROCESSED, true) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/RealmLiveEntityObserver.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/RealmLiveEntityObserver.kt index 50eb086f9a..f2f88e216b 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/RealmLiveEntityObserver.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/RealmLiveEntityObserver.kt @@ -35,7 +35,7 @@ import java.util.concurrent.atomic.AtomicReference internal interface LiveEntityObserver : SessionLifecycleObserver internal abstract class RealmLiveEntityObserver(protected val realmConfiguration: RealmConfiguration) : - LiveEntityObserver, RealmChangeListener> { + LiveEntityObserver, RealmChangeListener> { private companion object { val BACKGROUND_HANDLER = createBackgroundHandler("LIVE_ENTITY_BACKGROUND") diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/RealmSessionProvider.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/RealmSessionProvider.kt index 8c62c345d0..e5b5915590 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/RealmSessionProvider.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/RealmSessionProvider.kt @@ -33,7 +33,7 @@ import kotlin.concurrent.getOrSet */ @SessionScope internal class RealmSessionProvider @Inject constructor(@SessionDatabase private val monarchy: Monarchy) : - SessionLifecycleObserver { + SessionLifecycleObserver { private val realmThreadLocal = ThreadLocal() diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/helper/ChunkEntityHelper.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/helper/ChunkEntityHelper.kt index d2e3e99b75..4fdedabd70 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 @@ -118,7 +118,7 @@ internal fun ChunkEntity.addTimelineEvent(roomId: String, return timelineEventEntity } -fun computeIsUnique( +internal fun computeIsUnique( realm: Realm, roomId: String, isLastForward: Boolean, @@ -226,6 +226,9 @@ internal fun ChunkEntity.isMoreRecentThan(chunkToCheck: ChunkEntity): Boolean { if (chunkToCheck.doesNextChunksVerifyCondition { it == this }) { return true } + if (this.doesNextChunksVerifyCondition { it == chunkToCheck }) { + return false + } // Otherwise check if this chunk is linked to last forward if (this.doesNextChunksVerifyCondition { it.isLastForward }) { return true 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 ee3008d40b..04cf5b78af 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 @@ -16,9 +16,12 @@ package org.matrix.android.sdk.internal.database.helper +import com.squareup.moshi.JsonDataException import io.realm.Realm import io.realm.RealmQuery import io.realm.Sort +import org.matrix.android.sdk.api.session.events.model.UnsignedData +import org.matrix.android.sdk.api.session.events.model.isRedacted import org.matrix.android.sdk.api.session.room.timeline.TimelineEvent import org.matrix.android.sdk.api.session.threads.ThreadNotificationState import org.matrix.android.sdk.internal.database.mapper.asDomain @@ -33,6 +36,8 @@ import org.matrix.android.sdk.internal.database.query.findIncludingEvent import org.matrix.android.sdk.internal.database.query.findLastForwardChunkOfRoom import org.matrix.android.sdk.internal.database.query.where import org.matrix.android.sdk.internal.database.query.whereRoomId +import org.matrix.android.sdk.internal.di.MoshiProvider +import timber.log.Timber private typealias Summary = Pair? @@ -48,14 +53,14 @@ internal fun Map.updateThreadSummaryIfNeeded( for ((rootThreadEventId, eventEntity) in this) { eventEntity.threadSummaryInThread(eventEntity.realm, rootThreadEventId, chunkEntity)?.let { threadSummary -> - val numberOfMessages = threadSummary.first + val inThreadMessages = threadSummary.first val latestEventInThread = threadSummary.second // If this is a thread message, find its root event if exists val rootThreadEvent = if (eventEntity.isThread()) eventEntity.findRootThreadEvent() else eventEntity rootThreadEvent?.markEventAsRoot( - threadsCounted = numberOfMessages, + inThreadMessages = inThreadMessages, latestMessageTimelineEventEntity = latestEventInThread ) } @@ -81,28 +86,27 @@ internal fun EventEntity.findRootThreadEvent(): EventEntity? = * Mark or update the current event a root thread event */ internal fun EventEntity.markEventAsRoot( - threadsCounted: Int, + inThreadMessages: Int, latestMessageTimelineEventEntity: TimelineEventEntity?) { isRootThread = true - numberOfThreads = threadsCounted + numberOfThreads = inThreadMessages threadSummaryLatestMessage = latestMessageTimelineEventEntity } /** * Count the number of threads for the provided root thread eventId, and finds the latest event message + * note: Redactions are handled by RedactionEventProcessor * @param rootThreadEventId The root eventId that will find the number of threads * @return A ThreadSummary containing the counted threads and the latest event message */ internal fun EventEntity.threadSummaryInThread(realm: Realm, rootThreadEventId: String, chunkEntity: ChunkEntity?): Summary { - // Number of messages - val messages = TimelineEventEntity - .whereRoomId(realm, roomId = roomId) - .equalTo(TimelineEventEntityFields.ROOT.ROOT_THREAD_EVENT_ID, rootThreadEventId) - .distinct(TimelineEventEntityFields.ROOT.EVENT_ID) - .count() - .toInt() + val inThreadMessages = countInThreadMessages( + realm = realm, + roomId = roomId, + rootThreadEventId = rootThreadEventId + ) - if (messages <= 0) return null + if (inThreadMessages <= 0) return null // Find latest thread event, we know it exists var chunk = ChunkEntity.findLastForwardChunkOfRoom(realm, roomId) ?: chunkEntity ?: return null @@ -124,9 +128,38 @@ internal fun EventEntity.threadSummaryInThread(realm: Realm, rootThreadEventId: result ?: return null - return Summary(messages, result) + return Summary(inThreadMessages, result) } +/** + * Counts the number of thread replies in the main timeline thread summary, + * with respect to redactions. + */ +internal fun countInThreadMessages(realm: Realm, roomId: String, rootThreadEventId: String): Int = + TimelineEventEntity + .whereRoomId(realm, roomId = roomId) + .equalTo(TimelineEventEntityFields.ROOT.ROOT_THREAD_EVENT_ID, rootThreadEventId) + .distinct(TimelineEventEntityFields.ROOT.EVENT_ID) + .findAll() + .filterNot { timelineEvent -> + timelineEvent.root + ?.unsignedData + ?.takeIf { it.isNotBlank() } + ?.toUnsignedData() + .isRedacted() + }.size + +/** + * Mapping string to UnsignedData using Moshi + */ +private fun String.toUnsignedData(): UnsignedData? = + try { + MoshiProvider.providesMoshi().adapter(UnsignedData::class.java).fromJson(this) + } catch (ex: JsonDataException) { + Timber.e(ex, "Failed to parse UnsignedData") + null + } + /** * Lets compare them in case user is moving forward in the timeline and we cannot know the * exact chunk sequence while currentChunk is not yet committed in the DB 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 bfe310cd8b..66a0309bf4 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 @@ -23,13 +23,13 @@ import io.realm.kotlin.createObject import kotlinx.coroutines.runBlocking import org.matrix.android.sdk.api.session.crypto.CryptoService import org.matrix.android.sdk.api.session.crypto.MXCryptoError +import org.matrix.android.sdk.api.session.crypto.model.OlmDecryptionResult import org.matrix.android.sdk.api.session.events.model.Event import org.matrix.android.sdk.api.session.events.model.EventType import org.matrix.android.sdk.api.session.room.model.RoomMemberContent import org.matrix.android.sdk.api.session.room.send.SendState import org.matrix.android.sdk.api.session.room.threads.model.ThreadSummary import org.matrix.android.sdk.api.session.room.threads.model.ThreadSummaryUpdateType -import org.matrix.android.sdk.internal.crypto.algorithms.olm.OlmDecryptionResult import org.matrix.android.sdk.internal.database.mapper.asDomain import org.matrix.android.sdk.internal.database.mapper.toEntity import org.matrix.android.sdk.internal.database.model.CurrentStateEventEntity diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/helper/TimelineEventEntityHelper.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/helper/TimelineEventEntityHelper.kt index ea508731b1..8a5d08cd30 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/helper/TimelineEventEntityHelper.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/helper/TimelineEventEntityHelper.kt @@ -19,9 +19,10 @@ package org.matrix.android.sdk.internal.database.helper import io.realm.Realm import org.matrix.android.sdk.internal.database.model.TimelineEventEntity import org.matrix.android.sdk.internal.database.model.TimelineEventEntityFields +import org.matrix.android.sdk.internal.database.query.where internal fun TimelineEventEntity.Companion.nextId(realm: Realm): Long { - val currentIdNum = realm.where(TimelineEventEntity::class.java).max(TimelineEventEntityFields.LOCAL_ID) + val currentIdNum = TimelineEventEntity.where(realm).max(TimelineEventEntityFields.LOCAL_ID) return if (currentIdNum == null) { 1 } else { diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/lightweight/LightweightSettingsStorage.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/lightweight/LightweightSettingsStorage.kt deleted file mode 100644 index 700b94a985..0000000000 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/lightweight/LightweightSettingsStorage.kt +++ /dev/null @@ -1,47 +0,0 @@ -/* - * 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.database.lightweight - -import android.content.Context -import androidx.core.content.edit -import androidx.preference.PreferenceManager -import javax.inject.Inject - -/** - * The purpose of this class is to provide an alternative and lightweight way to store settings/data - * on the sdi without using the database. This should be used just for sdk/user preferences and - * not for large data sets - */ - -class LightweightSettingsStorage @Inject constructor(context: Context) { - - private val sdkDefaultPrefs = PreferenceManager.getDefaultSharedPreferences(context.applicationContext) - - fun setThreadMessagesEnabled(enabled: Boolean) { - sdkDefaultPrefs.edit { - putBoolean(MATRIX_SDK_SETTINGS_THREAD_MESSAGES_ENABLED, enabled) - } - } - - fun areThreadMessagesEnabled(): Boolean { - return sdkDefaultPrefs.getBoolean(MATRIX_SDK_SETTINGS_THREAD_MESSAGES_ENABLED, false) - } - - companion object { - const val MATRIX_SDK_SETTINGS_THREAD_MESSAGES_ENABLED = "MATRIX_SDK_SETTINGS_THREAD_MESSAGES_ENABLED" - } -} 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 c3302f5ccb..3083df062e 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 @@ -18,6 +18,7 @@ package org.matrix.android.sdk.internal.database.mapper import com.squareup.moshi.JsonDataException import org.matrix.android.sdk.api.session.crypto.MXCryptoError +import org.matrix.android.sdk.api.session.crypto.model.OlmDecryptionResult import org.matrix.android.sdk.api.session.events.model.Event import org.matrix.android.sdk.api.session.events.model.EventType import org.matrix.android.sdk.api.session.events.model.UnsignedData @@ -26,7 +27,6 @@ import org.matrix.android.sdk.api.session.room.send.SendState import org.matrix.android.sdk.api.session.room.sender.SenderInfo import org.matrix.android.sdk.api.session.threads.ThreadDetails import org.matrix.android.sdk.api.session.threads.ThreadNotificationState -import org.matrix.android.sdk.internal.crypto.algorithms.olm.OlmDecryptionResult import org.matrix.android.sdk.internal.database.model.EventEntity import org.matrix.android.sdk.internal.di.MoshiProvider import timber.log.Timber diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/mapper/PushConditionMapper.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/mapper/PushConditionMapper.kt index 5c0a2ba902..6521bf62d9 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/mapper/PushConditionMapper.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/mapper/PushConditionMapper.kt @@ -16,7 +16,7 @@ package org.matrix.android.sdk.internal.database.mapper -import org.matrix.android.sdk.api.pushrules.rest.PushCondition +import org.matrix.android.sdk.api.session.pushrules.rest.PushCondition import org.matrix.android.sdk.internal.database.model.PushConditionEntity internal object PushConditionMapper { diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/mapper/PushRulesMapper.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/mapper/PushRulesMapper.kt index 12eff8efa1..0b07754126 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/mapper/PushRulesMapper.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/mapper/PushRulesMapper.kt @@ -17,9 +17,9 @@ package org.matrix.android.sdk.internal.database.mapper import com.squareup.moshi.Types import io.realm.RealmList -import org.matrix.android.sdk.api.pushrules.Kind -import org.matrix.android.sdk.api.pushrules.rest.PushCondition -import org.matrix.android.sdk.api.pushrules.rest.PushRule +import org.matrix.android.sdk.api.session.pushrules.Kind +import org.matrix.android.sdk.api.session.pushrules.rest.PushCondition +import org.matrix.android.sdk.api.session.pushrules.rest.PushRule import org.matrix.android.sdk.internal.database.model.PushRuleEntity import org.matrix.android.sdk.internal.di.MoshiProvider import timber.log.Timber diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/mapper/RoomSummaryMapper.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/mapper/RoomSummaryMapper.kt index 63b326096a..41faf30a82 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 @@ -16,7 +16,8 @@ package org.matrix.android.sdk.internal.database.mapper -import org.matrix.android.sdk.api.crypto.RoomEncryptionTrustLevel +import org.matrix.android.sdk.api.crypto.MXCRYPTO_ALGORITHM_MEGOLM +import org.matrix.android.sdk.api.session.crypto.model.RoomEncryptionTrustLevel import org.matrix.android.sdk.api.session.room.model.RoomEncryptionAlgorithm import org.matrix.android.sdk.api.session.room.model.RoomJoinRules import org.matrix.android.sdk.api.session.room.model.RoomSummary @@ -24,7 +25,6 @@ import org.matrix.android.sdk.api.session.room.model.SpaceChildInfo import org.matrix.android.sdk.api.session.room.model.SpaceParentInfo import org.matrix.android.sdk.api.session.room.model.tag.RoomTag import org.matrix.android.sdk.api.session.typing.TypingUsersTracker -import org.matrix.android.sdk.internal.crypto.MXCRYPTO_ALGORITHM_MEGOLM import org.matrix.android.sdk.internal.database.model.RoomSummaryEntity import org.matrix.android.sdk.internal.database.model.presence.toUserPresence import javax.inject.Inject diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/migration/MigrateSessionTo001.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/migration/MigrateSessionTo001.kt index 831c6280ad..7bed23066c 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/migration/MigrateSessionTo001.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/migration/MigrateSessionTo001.kt @@ -20,7 +20,7 @@ import io.realm.DynamicRealm import org.matrix.android.sdk.internal.database.model.RoomSummaryEntityFields import org.matrix.android.sdk.internal.util.database.RealmMigrator -class MigrateSessionTo001(realm: DynamicRealm) : RealmMigrator(realm, 1) { +internal class MigrateSessionTo001(realm: DynamicRealm) : RealmMigrator(realm, 1) { override fun doMigrate(realm: DynamicRealm) { // Add hasFailedSending in RoomSummary and a small warning icon on room list diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/migration/MigrateSessionTo002.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/migration/MigrateSessionTo002.kt index 215e558e2a..9fa36248f8 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/migration/MigrateSessionTo002.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/migration/MigrateSessionTo002.kt @@ -19,7 +19,7 @@ package org.matrix.android.sdk.internal.database.migration import io.realm.DynamicRealm import org.matrix.android.sdk.internal.util.database.RealmMigrator -class MigrateSessionTo002(realm: DynamicRealm) : RealmMigrator(realm, 2) { +internal class MigrateSessionTo002(realm: DynamicRealm) : RealmMigrator(realm, 2) { override fun doMigrate(realm: DynamicRealm) { realm.schema.get("HomeServerCapabilitiesEntity") diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/migration/MigrateSessionTo003.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/migration/MigrateSessionTo003.kt index bc0b79d7e6..b4aca53ece 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/migration/MigrateSessionTo003.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/migration/MigrateSessionTo003.kt @@ -20,7 +20,7 @@ import io.realm.DynamicRealm import org.matrix.android.sdk.internal.extensions.forceRefreshOfHomeServerCapabilities import org.matrix.android.sdk.internal.util.database.RealmMigrator -class MigrateSessionTo003(realm: DynamicRealm) : RealmMigrator(realm, 3) { +internal class MigrateSessionTo003(realm: DynamicRealm) : RealmMigrator(realm, 3) { override fun doMigrate(realm: DynamicRealm) { realm.schema.get("HomeServerCapabilitiesEntity") diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/migration/MigrateSessionTo004.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/migration/MigrateSessionTo004.kt index be13ae2c2f..0d91aab7d8 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/migration/MigrateSessionTo004.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/migration/MigrateSessionTo004.kt @@ -20,7 +20,7 @@ import io.realm.DynamicRealm import org.matrix.android.sdk.internal.database.model.PendingThreePidEntityFields import org.matrix.android.sdk.internal.util.database.RealmMigrator -class MigrateSessionTo004(realm: DynamicRealm) : RealmMigrator(realm, 4) { +internal class MigrateSessionTo004(realm: DynamicRealm) : RealmMigrator(realm, 4) { override fun doMigrate(realm: DynamicRealm) { realm.schema.create("PendingThreePidEntity") diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/migration/MigrateSessionTo005.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/migration/MigrateSessionTo005.kt index b4826b23a4..67e91d85cc 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/migration/MigrateSessionTo005.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/migration/MigrateSessionTo005.kt @@ -19,7 +19,7 @@ package org.matrix.android.sdk.internal.database.migration import io.realm.DynamicRealm import org.matrix.android.sdk.internal.util.database.RealmMigrator -class MigrateSessionTo005(realm: DynamicRealm) : RealmMigrator(realm, 5) { +internal class MigrateSessionTo005(realm: DynamicRealm) : RealmMigrator(realm, 5) { override fun doMigrate(realm: DynamicRealm) { realm.schema.get("HomeServerCapabilitiesEntity") diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/migration/MigrateSessionTo006.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/migration/MigrateSessionTo006.kt index 3d7f26ccee..8eccc229e7 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/migration/MigrateSessionTo006.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/migration/MigrateSessionTo006.kt @@ -20,7 +20,7 @@ import io.realm.DynamicRealm import org.matrix.android.sdk.internal.database.model.PreviewUrlCacheEntityFields import org.matrix.android.sdk.internal.util.database.RealmMigrator -class MigrateSessionTo006(realm: DynamicRealm) : RealmMigrator(realm, 6) { +internal class MigrateSessionTo006(realm: DynamicRealm) : RealmMigrator(realm, 6) { override fun doMigrate(realm: DynamicRealm) { realm.schema.create("PreviewUrlCacheEntity") diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/migration/MigrateSessionTo007.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/migration/MigrateSessionTo007.kt index be8c8ce9c6..5d1ff80367 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/migration/MigrateSessionTo007.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/migration/MigrateSessionTo007.kt @@ -21,7 +21,7 @@ import org.matrix.android.sdk.internal.database.model.RoomEntityFields import org.matrix.android.sdk.internal.database.model.RoomMembersLoadStatusType import org.matrix.android.sdk.internal.util.database.RealmMigrator -class MigrateSessionTo007(realm: DynamicRealm) : RealmMigrator(realm, 7) { +internal class MigrateSessionTo007(realm: DynamicRealm) : RealmMigrator(realm, 7) { override fun doMigrate(realm: DynamicRealm) { realm.schema.get("RoomEntity") diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/migration/MigrateSessionTo008.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/migration/MigrateSessionTo008.kt index d46730ef70..b61bf7e6fa 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/migration/MigrateSessionTo008.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/migration/MigrateSessionTo008.kt @@ -21,7 +21,7 @@ import org.matrix.android.sdk.internal.database.model.EditAggregatedSummaryEntit import org.matrix.android.sdk.internal.database.model.EditionOfEventFields import org.matrix.android.sdk.internal.util.database.RealmMigrator -class MigrateSessionTo008(realm: DynamicRealm) : RealmMigrator(realm, 8) { +internal class MigrateSessionTo008(realm: DynamicRealm) : RealmMigrator(realm, 8) { override fun doMigrate(realm: DynamicRealm) { val editionOfEventSchema = realm.schema.create("EditionOfEvent") diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/migration/MigrateSessionTo009.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/migration/MigrateSessionTo009.kt index 370430b9e3..149d322f66 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/migration/MigrateSessionTo009.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/migration/MigrateSessionTo009.kt @@ -25,7 +25,7 @@ import org.matrix.android.sdk.internal.database.model.RoomTagEntityFields import org.matrix.android.sdk.internal.database.model.TimelineEventEntityFields import org.matrix.android.sdk.internal.util.database.RealmMigrator -class MigrateSessionTo009(realm: DynamicRealm) : RealmMigrator(realm, 9) { +internal class MigrateSessionTo009(realm: DynamicRealm) : RealmMigrator(realm, 9) { 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/MigrateSessionTo010.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/migration/MigrateSessionTo010.kt index b968862d10..aae80423ac 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/migration/MigrateSessionTo010.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/migration/MigrateSessionTo010.kt @@ -27,7 +27,7 @@ import org.matrix.android.sdk.internal.database.model.SpaceParentSummaryEntityFi import org.matrix.android.sdk.internal.di.MoshiProvider import org.matrix.android.sdk.internal.util.database.RealmMigrator -class MigrateSessionTo010(realm: DynamicRealm) : RealmMigrator(realm, 10) { +internal class MigrateSessionTo010(realm: DynamicRealm) : RealmMigrator(realm, 10) { override fun doMigrate(realm: DynamicRealm) { realm.schema.create("SpaceChildSummaryEntity") diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/migration/MigrateSessionTo011.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/migration/MigrateSessionTo011.kt index 92ee26df42..5ba201dd46 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/migration/MigrateSessionTo011.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/migration/MigrateSessionTo011.kt @@ -20,7 +20,7 @@ import io.realm.DynamicRealm import org.matrix.android.sdk.internal.database.model.EventEntityFields import org.matrix.android.sdk.internal.util.database.RealmMigrator -class MigrateSessionTo011(realm: DynamicRealm) : RealmMigrator(realm, 11) { +internal class MigrateSessionTo011(realm: DynamicRealm) : RealmMigrator(realm, 11) { override fun doMigrate(realm: DynamicRealm) { realm.schema.get("EventEntity") diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/migration/MigrateSessionTo012.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/migration/MigrateSessionTo012.kt index a914cadd80..f72cd3064f 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/migration/MigrateSessionTo012.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/migration/MigrateSessionTo012.kt @@ -26,7 +26,7 @@ import org.matrix.android.sdk.internal.database.model.SpaceChildSummaryEntityFie import org.matrix.android.sdk.internal.di.MoshiProvider import org.matrix.android.sdk.internal.util.database.RealmMigrator -class MigrateSessionTo012(realm: DynamicRealm) : RealmMigrator(realm, 12) { +internal class MigrateSessionTo012(realm: DynamicRealm) : RealmMigrator(realm, 12) { override fun doMigrate(realm: DynamicRealm) { val joinRulesContentAdapter = MoshiProvider.providesMoshi().adapter(RoomJoinRulesContent::class.java) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/migration/MigrateSessionTo013.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/migration/MigrateSessionTo013.kt index 2ea0303802..2823a69ff1 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/migration/MigrateSessionTo013.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/migration/MigrateSessionTo013.kt @@ -20,7 +20,7 @@ import io.realm.DynamicRealm import org.matrix.android.sdk.internal.database.model.SpaceChildSummaryEntityFields import org.matrix.android.sdk.internal.util.database.RealmMigrator -class MigrateSessionTo013(realm: DynamicRealm) : RealmMigrator(realm, 13) { +internal class MigrateSessionTo013(realm: DynamicRealm) : RealmMigrator(realm, 13) { override fun doMigrate(realm: DynamicRealm) { // Fix issue with the nightly build. Eventually play again the migration which has been included in migrateTo12() diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/migration/MigrateSessionTo014.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/migration/MigrateSessionTo014.kt index c524b6f284..4a27c8bb12 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/migration/MigrateSessionTo014.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/migration/MigrateSessionTo014.kt @@ -24,7 +24,7 @@ import org.matrix.android.sdk.internal.database.model.RoomEntityFields import org.matrix.android.sdk.internal.database.model.RoomSummaryEntityFields import org.matrix.android.sdk.internal.util.database.RealmMigrator -class MigrateSessionTo014(realm: DynamicRealm) : RealmMigrator(realm, 14) { +internal class MigrateSessionTo014(realm: DynamicRealm) : RealmMigrator(realm, 14) { override fun doMigrate(realm: DynamicRealm) { val roomAccountDataSchema = realm.schema.create("RoomAccountDataEntity") diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/migration/MigrateSessionTo015.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/migration/MigrateSessionTo015.kt index 329964a9a4..f45f9b39b1 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/migration/MigrateSessionTo015.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/migration/MigrateSessionTo015.kt @@ -22,7 +22,7 @@ import org.matrix.android.sdk.internal.database.model.RoomSummaryEntityFields import org.matrix.android.sdk.internal.query.process import org.matrix.android.sdk.internal.util.database.RealmMigrator -class MigrateSessionTo015(realm: DynamicRealm) : RealmMigrator(realm, 15) { +internal class MigrateSessionTo015(realm: DynamicRealm) : RealmMigrator(realm, 15) { override fun doMigrate(realm: DynamicRealm) { // fix issue with flattenParentIds on DM that kept growing with duplicate diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/migration/MigrateSessionTo016.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/migration/MigrateSessionTo016.kt index b2fa54a05c..69f6c9f172 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/migration/MigrateSessionTo016.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/migration/MigrateSessionTo016.kt @@ -21,7 +21,7 @@ import org.matrix.android.sdk.internal.database.model.HomeServerCapabilitiesEnti import org.matrix.android.sdk.internal.extensions.forceRefreshOfHomeServerCapabilities import org.matrix.android.sdk.internal.util.database.RealmMigrator -class MigrateSessionTo016(realm: DynamicRealm) : RealmMigrator(realm, 16) { +internal class MigrateSessionTo016(realm: DynamicRealm) : RealmMigrator(realm, 16) { override fun doMigrate(realm: DynamicRealm) { realm.schema.get("HomeServerCapabilitiesEntity") diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/migration/MigrateSessionTo017.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/migration/MigrateSessionTo017.kt index 95d67b9ad8..4d8db92b69 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/migration/MigrateSessionTo017.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/migration/MigrateSessionTo017.kt @@ -20,7 +20,7 @@ import io.realm.DynamicRealm import org.matrix.android.sdk.internal.database.model.EventInsertEntityFields import org.matrix.android.sdk.internal.util.database.RealmMigrator -class MigrateSessionTo017(realm: DynamicRealm) : RealmMigrator(realm, 17) { +internal class MigrateSessionTo017(realm: DynamicRealm) : RealmMigrator(realm, 17) { override fun doMigrate(realm: DynamicRealm) { realm.schema.get("EventInsertEntity") diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/migration/MigrateSessionTo018.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/migration/MigrateSessionTo018.kt index b415c51d4b..559b8979e1 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/migration/MigrateSessionTo018.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/migration/MigrateSessionTo018.kt @@ -22,7 +22,7 @@ import org.matrix.android.sdk.internal.database.model.RoomSummaryEntityFields import org.matrix.android.sdk.internal.database.model.presence.UserPresenceEntityFields import org.matrix.android.sdk.internal.util.database.RealmMigrator -class MigrateSessionTo018(realm: DynamicRealm) : RealmMigrator(realm, 18) { +internal class MigrateSessionTo018(realm: DynamicRealm) : RealmMigrator(realm, 18) { override fun doMigrate(realm: DynamicRealm) { realm.schema.create("UserPresenceEntity") 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 d0b368be46..754a66bb4b 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/migration/MigrateSessionTo019.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/migration/MigrateSessionTo019.kt @@ -21,8 +21,8 @@ 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 -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/MigrateSessionTo020.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/migration/MigrateSessionTo020.kt index c7f6e3ceed..e0075894ed 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/migration/MigrateSessionTo020.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/migration/MigrateSessionTo020.kt @@ -20,7 +20,7 @@ import io.realm.DynamicRealm import org.matrix.android.sdk.internal.database.model.ChunkEntityFields import org.matrix.android.sdk.internal.util.database.RealmMigrator -class MigrateSessionTo020(realm: DynamicRealm) : RealmMigrator(realm, 20) { +internal class MigrateSessionTo020(realm: DynamicRealm) : RealmMigrator(realm, 20) { override fun doMigrate(realm: DynamicRealm) { realm.schema.get("ChunkEntity")?.apply { diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/migration/MigrateSessionTo021.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/migration/MigrateSessionTo021.kt index 6b6952e697..2f880a29dc 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/migration/MigrateSessionTo021.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/migration/MigrateSessionTo021.kt @@ -18,14 +18,14 @@ package org.matrix.android.sdk.internal.database.migration import io.realm.DynamicRealm import org.matrix.android.sdk.api.session.events.model.EventType -import org.matrix.android.sdk.internal.crypto.model.event.EncryptionEventContent +import org.matrix.android.sdk.api.session.events.model.content.EncryptionEventContent import org.matrix.android.sdk.internal.database.model.CurrentStateEventEntityFields import org.matrix.android.sdk.internal.database.model.EventEntityFields import org.matrix.android.sdk.internal.database.model.RoomSummaryEntityFields import org.matrix.android.sdk.internal.di.MoshiProvider import org.matrix.android.sdk.internal.util.database.RealmMigrator -class MigrateSessionTo021(realm: DynamicRealm) : RealmMigrator(realm, 21) { +internal class MigrateSessionTo021(realm: DynamicRealm) : RealmMigrator(realm, 21) { 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/MigrateSessionTo022.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/migration/MigrateSessionTo022.kt index e78a9d05da..f55700d36d 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/migration/MigrateSessionTo022.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/migration/MigrateSessionTo022.kt @@ -23,7 +23,7 @@ import org.matrix.android.sdk.internal.database.model.RoomEntityFields import org.matrix.android.sdk.internal.util.database.RealmMigrator import timber.log.Timber -class MigrateSessionTo022(realm: DynamicRealm) : RealmMigrator(realm, 22) { +internal class MigrateSessionTo022(realm: DynamicRealm) : RealmMigrator(realm, 22) { override fun doMigrate(realm: DynamicRealm) { val listJoinedRoomIds = realm.where("RoomEntity") diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/migration/MigrateSessionTo023.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/migration/MigrateSessionTo023.kt index 0bb8ceeaa5..a3ce0b5414 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/migration/MigrateSessionTo023.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/migration/MigrateSessionTo023.kt @@ -22,7 +22,7 @@ import org.matrix.android.sdk.api.session.threads.ThreadNotificationState import org.matrix.android.sdk.internal.database.model.EventEntityFields import org.matrix.android.sdk.internal.util.database.RealmMigrator -class MigrateSessionTo023(realm: DynamicRealm) : RealmMigrator(realm, 23) { +internal class MigrateSessionTo023(realm: DynamicRealm) : RealmMigrator(realm, 23) { override fun doMigrate(realm: DynamicRealm) { val eventEntity = realm.schema.get("TimelineEventEntity") ?: return diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/migration/MigrateSessionTo024.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/migration/MigrateSessionTo024.kt index ff88972566..fc17bf9b28 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/migration/MigrateSessionTo024.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/migration/MigrateSessionTo024.kt @@ -20,7 +20,7 @@ import io.realm.DynamicRealm import org.matrix.android.sdk.internal.database.model.PreviewUrlCacheEntityFields import org.matrix.android.sdk.internal.util.database.RealmMigrator -class MigrateSessionTo024(realm: DynamicRealm) : RealmMigrator(realm, 24) { +internal class MigrateSessionTo024(realm: DynamicRealm) : RealmMigrator(realm, 24) { override fun doMigrate(realm: DynamicRealm) { realm.schema.get("PreviewUrlCacheEntity") diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/migration/MigrateSessionTo025.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/migration/MigrateSessionTo025.kt index 237b016ac2..a57fd52ec1 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/migration/MigrateSessionTo025.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/migration/MigrateSessionTo025.kt @@ -21,7 +21,7 @@ import org.matrix.android.sdk.internal.database.model.HomeServerCapabilitiesEnti import org.matrix.android.sdk.internal.extensions.forceRefreshOfHomeServerCapabilities import org.matrix.android.sdk.internal.util.database.RealmMigrator -class MigrateSessionTo025(realm: DynamicRealm) : RealmMigrator(realm, 25) { +internal class MigrateSessionTo025(realm: DynamicRealm) : RealmMigrator(realm, 25) { override fun doMigrate(realm: DynamicRealm) { realm.schema.get("HomeServerCapabilitiesEntity") diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/migration/MigrateSessionTo026.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/migration/MigrateSessionTo026.kt index f108a91ecf..35a6135ba2 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/migration/MigrateSessionTo026.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/migration/MigrateSessionTo026.kt @@ -31,7 +31,7 @@ import org.matrix.android.sdk.internal.util.database.RealmMigrator * Live thread list: using enhanced /messages api MSC3440 * Live thread timeline: using /relations api */ -class MigrateSessionTo026(realm: DynamicRealm) : RealmMigrator(realm, 26) { +internal class MigrateSessionTo026(realm: DynamicRealm) : RealmMigrator(realm, 26) { override fun doMigrate(realm: DynamicRealm) { realm.schema.get("ChunkEntity") diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/ChunkEntity.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/ChunkEntity.kt index 88eb821aa9..822bc1bd8f 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/ChunkEntity.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/ChunkEntity.kt @@ -24,19 +24,20 @@ import io.realm.annotations.LinkingObjects import org.matrix.android.sdk.internal.extensions.assertIsManaged import org.matrix.android.sdk.internal.extensions.clearWith -internal open class ChunkEntity(@Index var prevToken: String? = null, +internal open class ChunkEntity( + @Index var prevToken: String? = null, // Because of gaps we can have several chunks with nextToken == null - @Index var nextToken: String? = null, - var prevChunk: ChunkEntity? = null, - var nextChunk: ChunkEntity? = null, - var stateEvents: RealmList = RealmList(), - var timelineEvents: RealmList = RealmList(), + @Index var nextToken: String? = null, + var prevChunk: ChunkEntity? = null, + var nextChunk: ChunkEntity? = null, + var stateEvents: RealmList = RealmList(), + var timelineEvents: RealmList = RealmList(), // Only one chunk will have isLastForward == true - @Index var isLastForward: Boolean = false, - @Index var isLastBackward: Boolean = false, + @Index var isLastForward: Boolean = false, + @Index var isLastBackward: Boolean = false, // Threads - @Index var rootThreadEventId: String? = null, - @Index var isLastForwardThread: Boolean = false, + @Index var rootThreadEventId: String? = null, + @Index var isLastForwardThread: Boolean = false, ) : RealmObject() { fun identifier() = "${prevToken}_$nextToken" diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/EventEntity.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/EventEntity.kt index b7158ba9cd..ba80cc8302 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 @@ -18,10 +18,10 @@ package org.matrix.android.sdk.internal.database.model import io.realm.RealmObject import io.realm.annotations.Index +import org.matrix.android.sdk.api.session.crypto.model.MXEventDecryptionResult +import org.matrix.android.sdk.api.session.crypto.model.OlmDecryptionResult import org.matrix.android.sdk.api.session.room.send.SendState import org.matrix.android.sdk.api.session.threads.ThreadNotificationState -import org.matrix.android.sdk.internal.crypto.MXEventDecryptionResult -import org.matrix.android.sdk.internal.crypto.algorithms.olm.OlmDecryptionResult import org.matrix.android.sdk.internal.di.MoshiProvider import org.matrix.android.sdk.internal.extensions.assertIsManaged @@ -44,6 +44,7 @@ internal open class EventEntity(@Index var eventId: String = "", // Thread related, no need to create a new Entity for performance @Index var isRootThread: Boolean = false, @Index var rootThreadEventId: String? = null, + // Number messages within the thread var numberOfThreads: Int = 0, var threadSummaryLatestMessage: TimelineEventEntity? = null ) : RealmObject() { diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/GroupEntity.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/GroupEntity.kt index 527f782359..0120bb91d3 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/GroupEntity.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/GroupEntity.kt @@ -25,7 +25,7 @@ import org.matrix.android.sdk.api.session.room.model.Membership * Then GetGroupDataTask is called regularly to fetch group information from the homeserver. */ internal open class GroupEntity(@PrimaryKey var groupId: String = "") : - RealmObject() { + RealmObject() { private var membershipStr: String = Membership.NONE.name var membership: Membership diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/PushRulesEntity.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/PushRulesEntity.kt index 4125d90891..62bf40c1d2 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/PushRulesEntity.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/PushRulesEntity.kt @@ -17,7 +17,7 @@ package org.matrix.android.sdk.internal.database.model import io.realm.RealmList import io.realm.RealmObject -import org.matrix.android.sdk.api.pushrules.RuleKind +import org.matrix.android.sdk.api.session.pushrules.RuleKind import org.matrix.android.sdk.internal.extensions.clearWith internal open class PushRulesEntity( diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/RoomEntity.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/RoomEntity.kt index 4a6f6a7bf8..d8e6b8af0f 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/RoomEntity.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/RoomEntity.kt @@ -48,8 +48,10 @@ internal open class RoomEntity(@PrimaryKey var roomId: String = "", set(value) { membersLoadStatusStr = value.name } + companion object } + internal fun RoomEntity.removeThreadSummaryIfNeeded(eventId: String) { assertIsManaged() threadSummaries.findRootOrLatest(eventId)?.let { diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/RoomSummaryEntity.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/RoomSummaryEntity.kt index febedc3456..cd755590be 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/RoomSummaryEntity.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/RoomSummaryEntity.kt @@ -20,8 +20,8 @@ import io.realm.RealmList import io.realm.RealmObject import io.realm.annotations.Index import io.realm.annotations.PrimaryKey -import org.matrix.android.sdk.api.crypto.RoomEncryptionTrustLevel import org.matrix.android.sdk.api.extensions.tryOrNull +import org.matrix.android.sdk.api.session.crypto.model.RoomEncryptionTrustLevel import org.matrix.android.sdk.api.session.room.model.Membership import org.matrix.android.sdk.api.session.room.model.RoomJoinRules import org.matrix.android.sdk.api.session.room.model.RoomSummary diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/TimelineEventEntity.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/TimelineEventEntity.kt index aacd6570bc..477c04fe51 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/TimelineEventEntity.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/TimelineEventEntity.kt @@ -32,8 +32,8 @@ internal open class TimelineEventEntity(var localId: Long = 0, var isUniqueDisplayName: Boolean = false, var senderAvatar: String? = null, var senderMembershipEventId: String? = null, - // ownedByThreadChunk indicates that the current TimelineEventEntity belongs - // to a thread chunk and is a temporarily event. + // ownedByThreadChunk indicates that the current TimelineEventEntity belongs + // to a thread chunk and is a temporarily event. var ownedByThreadChunk: Boolean = false, var readReceipts: ReadReceiptsSummaryEntity? = null ) : RealmObject() { diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/query/ChunkEntityQueries.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/query/ChunkEntityQueries.kt index ece46555a7..9350102137 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/query/ChunkEntityQueries.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/query/ChunkEntityQueries.kt @@ -40,23 +40,37 @@ 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? { + val query = where(realm, roomId) + if (prevToken != null) { + query.equalTo(ChunkEntityFields.PREV_TOKEN, prevToken) + } + if (nextToken != null) { + query.equalTo(ChunkEntityFields.NEXT_TOKEN, nextToken) + } + return query.findAll() +} + internal fun ChunkEntity.Companion.findLastForwardChunkOfRoom(realm: Realm, roomId: String): ChunkEntity? { return where(realm, roomId) .equalTo(ChunkEntityFields.IS_LAST_FORWARD, true) .findFirst() } + internal fun ChunkEntity.Companion.findLastForwardChunkOfThread(realm: Realm, roomId: String, rootThreadEventId: String): ChunkEntity? { return where(realm, roomId) .equalTo(ChunkEntityFields.ROOT_THREAD_EVENT_ID, rootThreadEventId) .equalTo(ChunkEntityFields.IS_LAST_FORWARD_THREAD, true) .findFirst() } + internal fun ChunkEntity.Companion.findEventInThreadChunk(realm: Realm, roomId: String, event: String): ChunkEntity? { return where(realm, roomId) .`in`(ChunkEntityFields.TIMELINE_EVENTS.EVENT_ID, arrayListOf(event).toTypedArray()) .equalTo(ChunkEntityFields.IS_LAST_FORWARD_THREAD, true) .findFirst() } + internal fun ChunkEntity.Companion.findAllIncludingEvents(realm: Realm, eventIds: List): RealmResults { return realm.where() .`in`(ChunkEntityFields.TIMELINE_EVENTS.EVENT_ID, eventIds.toTypedArray()) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/query/PushersQueries.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/query/PushersQueries.kt index 1f6b210252..3cea19a690 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/query/PushersQueries.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/query/PushersQueries.kt @@ -18,7 +18,7 @@ package org.matrix.android.sdk.internal.database.query import io.realm.Realm import io.realm.RealmQuery import io.realm.kotlin.where -import org.matrix.android.sdk.api.pushrules.RuleKind +import org.matrix.android.sdk.api.session.pushrules.RuleKind import org.matrix.android.sdk.internal.database.model.PushRuleEntity import org.matrix.android.sdk.internal.database.model.PushRuleEntityFields import org.matrix.android.sdk.internal.database.model.PushRulesEntity diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/query/ReadQueries.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/query/ReadQueries.kt index 8cc99c3d2f..6c587dfcae 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 @@ -94,7 +94,7 @@ internal fun isReadMarkerMoreRecent(realmConfiguration: RealmConfiguration, val eventToCheckIndex = eventToCheck?.displayIndex ?: Int.MAX_VALUE eventToCheckIndex <= readMarkerIndex } else { - eventToCheckChunk?.isLastForward == false + eventToCheckChunk != null && readMarkerChunk?.isMoreRecentThan(eventToCheckChunk) == true } } } 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 d1b05a4932..8993c36a30 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 @@ -49,6 +49,10 @@ internal fun RoomSummaryEntity.Companion.getOrCreate(realm: Realm, roomId: Strin return where(realm, roomId).findFirst() ?: realm.createObject(roomId) } +internal fun RoomSummaryEntity.Companion.getOrNull(realm: Realm, roomId: String): RoomSummaryEntity? { + return where(realm, roomId).findFirst() +} + internal fun RoomSummaryEntity.Companion.getDirectRooms(realm: Realm, excludeRoomIds: Set? = null): RealmResults { return RoomSummaryEntity.where(realm) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/query/ThreadSummaryEntityQueries.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/query/ThreadSummaryEntityQueries.kt index 517d43d7cf..eab2740433 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/query/ThreadSummaryEntityQueries.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/query/ThreadSummaryEntityQueries.kt @@ -39,9 +39,11 @@ internal fun ThreadSummaryEntity.Companion.getOrCreate(realm: Realm, roomId: Str this.rootThreadEventId = rootThreadEventId } } + internal fun ThreadSummaryEntity.Companion.getOrNull(realm: Realm, roomId: String, rootThreadEventId: String): ThreadSummaryEntity? { return where(realm, roomId, rootThreadEventId).findFirst() } + internal fun RealmList.find(rootThreadEventId: String): ThreadSummaryEntity? { return this.where() .equalTo(ThreadSummaryEntityFields.ROOT_THREAD_EVENT_ID, rootThreadEventId) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/query/TimelineEventEntityQueries.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/query/TimelineEventEntityQueries.kt index 81d5ac835f..215ab34f95 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/query/TimelineEventEntityQueries.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/query/TimelineEventEntityQueries.kt @@ -29,26 +29,35 @@ import org.matrix.android.sdk.internal.database.model.RoomEntity import org.matrix.android.sdk.internal.database.model.TimelineEventEntity import org.matrix.android.sdk.internal.database.model.TimelineEventEntityFields -internal fun TimelineEventEntity.Companion.where(realm: Realm, roomId: String, eventId: String): RealmQuery { - return realm.where() +internal fun TimelineEventEntity.Companion.where(realm: Realm): RealmQuery { + return realm.where() +} + +internal fun TimelineEventEntity.Companion.where(realm: Realm, + roomId: String, + eventId: String): RealmQuery { + return where(realm) .equalTo(TimelineEventEntityFields.ROOM_ID, roomId) .equalTo(TimelineEventEntityFields.EVENT_ID, eventId) } -internal fun TimelineEventEntity.Companion.where(realm: Realm, roomId: String, eventIds: List): RealmQuery { - return realm.where() +internal fun TimelineEventEntity.Companion.where(realm: Realm, + roomId: String, + eventIds: List): RealmQuery { + return where(realm) .equalTo(TimelineEventEntityFields.ROOM_ID, roomId) .`in`(TimelineEventEntityFields.EVENT_ID, eventIds.toTypedArray()) } internal fun TimelineEventEntity.Companion.whereRoomId(realm: Realm, roomId: String): RealmQuery { - return realm.where() + return where(realm) .equalTo(TimelineEventEntityFields.ROOM_ID, roomId) } -internal fun TimelineEventEntity.Companion.findWithSenderMembershipEvent(realm: Realm, senderMembershipEventId: String): List { - return realm.where() +internal fun TimelineEventEntity.Companion.findWithSenderMembershipEvent(realm: Realm, + senderMembershipEventId: String): List { + return where(realm) .equalTo(TimelineEventEntityFields.SENDER_MEMBERSHIP_EVENT_ID, senderMembershipEventId) .findAll() } @@ -110,12 +119,12 @@ internal fun RealmQuery.filterTypes(filterTypes: List.find(eventId: String): TimelineEventEntity? { - return this.where() + return where() .equalTo(TimelineEventEntityFields.EVENT_ID, eventId) .findFirst() } @@ -132,3 +141,14 @@ internal fun RealmQuery.filterSendStates(sendStates: List): RealmResults { + return where(realm) + .`in`(TimelineEventEntityFields.ROOT.SENDER, senderIds.toTypedArray()) + .isNull(TimelineEventEntityFields.ROOT.STATE_KEY) + .findAll() +} diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/di/MatrixComponent.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/di/MatrixComponent.kt index d9a4f1bde1..2fad2d8e78 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,12 +28,14 @@ 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.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.raw.RawModule import org.matrix.android.sdk.internal.session.MockHttpInterceptor import org.matrix.android.sdk.internal.session.TestInterceptor +import org.matrix.android.sdk.internal.settings.SettingsModule import org.matrix.android.sdk.internal.task.TaskExecutor import org.matrix.android.sdk.internal.util.BackgroundDetectionObserver import org.matrix.android.sdk.internal.util.system.SystemModule @@ -46,6 +48,7 @@ import java.io.File NetworkModule::class, AuthModule::class, RawModule::class, + SettingsModule::class, SystemModule::class, NoOpTestModule::class ]) @@ -66,6 +69,8 @@ internal interface MatrixComponent { fun rawService(): RawService + fun lightweightSettingsStorage(): LightweightSettingsStorage + fun homeServerHistoryService(): HomeServerHistoryService fun context(): Context diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/di/MoshiProvider.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/di/MoshiProvider.kt index 9e50e9efe8..10b0d4fb13 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/di/MoshiProvider.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/di/MoshiProvider.kt @@ -37,7 +37,7 @@ import org.matrix.android.sdk.internal.network.parsing.TlsVersionMoshiAdapter import org.matrix.android.sdk.internal.network.parsing.UriMoshiAdapter import org.matrix.android.sdk.internal.session.sync.parsing.DefaultLazyRoomSyncEphemeralJsonAdapter -object MoshiProvider { +internal object MoshiProvider { private val moshi: Moshi = Moshi.Builder() .add(UriMoshiAdapter()) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/di/SerializeNulls.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/di/SerializeNulls.kt index 0d8fdde813..9bd197e42e 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/di/SerializeNulls.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/di/SerializeNulls.kt @@ -25,7 +25,7 @@ import java.lang.reflect.Type @Retention(AnnotationRetention.RUNTIME) @JsonQualifier -annotation class SerializeNulls { +internal annotation class SerializeNulls { companion object { val JSON_ADAPTER_FACTORY: JsonAdapter.Factory = object : JsonAdapter.Factory { @Nullable 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 7d004bc5c0..fedd7d05f9 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 @@ -80,8 +80,8 @@ internal class WorkManagerProvider @Inject constructor( workManager.enqueue(checkWorkerRequest) val checkWorkerLiveState = workManager.getWorkInfoByIdLiveData(checkWorkerRequest.id) val observer = object : Observer { - override fun onChanged(workInfo: WorkInfo) { - if (workInfo.state.isFinished) { + override fun onChanged(workInfo: WorkInfo?) { + if (workInfo?.state?.isFinished == true) { checkWorkerLiveState.removeObserver(this) if (workInfo.state == WorkInfo.State.FAILED) { throw RuntimeException("MatrixWorkerFactory is not being set on your worker configuration.\n" + diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/extensions/LiveData.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/extensions/LiveData.kt index fecbb874d0..8f57960b95 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/extensions/LiveData.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/extensions/LiveData.kt @@ -18,35 +18,7 @@ package org.matrix.android.sdk.internal.extensions import androidx.lifecycle.LifecycleOwner import androidx.lifecycle.LiveData -import androidx.lifecycle.MediatorLiveData -import androidx.lifecycle.Observer -inline fun LiveData.observeK(owner: LifecycleOwner, crossinline observer: (T?) -> Unit) { - this.observe(owner, Observer { observer(it) }) -} - -inline fun LiveData.observeNotNull(owner: LifecycleOwner, crossinline observer: (T) -> Unit) { - this.observe(owner, Observer { it?.run(observer) }) -} - -fun combineLatest(source1: LiveData, source2: LiveData, mapper: (T1, T2) -> R): LiveData { - val combined = MediatorLiveData() - var source1Result: T1? = null - var source2Result: T2? = null - - fun notify() { - if (source1Result != null && source2Result != null) { - combined.value = mapper(source1Result!!, source2Result!!) - } - } - - combined.addSource(source1) { - source1Result = it - notify() - } - combined.addSource(source2) { - source2Result = it - notify() - } - return combined +internal inline fun LiveData.observeNotNull(owner: LifecycleOwner, crossinline observer: (T) -> Unit) { + this.observe(owner) { it?.run(observer) } } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/extensions/Primitives.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/extensions/Primitives.kt index 855e7edac3..290f06142c 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/extensions/Primitives.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/extensions/Primitives.kt @@ -19,4 +19,4 @@ package org.matrix.android.sdk.internal.extensions /** * Convert a signed byte to a int value */ -fun Byte.toUnsignedInt() = toInt() and 0xff +internal fun Byte.toUnsignedInt() = toInt() and 0xff diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/extensions/Result.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/extensions/Result.kt index 3734c5dc1d..b85102ef17 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/extensions/Result.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/extensions/Result.kt @@ -17,7 +17,7 @@ package org.matrix.android.sdk.internal.extensions import org.matrix.android.sdk.api.MatrixCallback -fun Result.foldToCallback(callback: MatrixCallback): Unit = fold( +internal fun Result.foldToCallback(callback: MatrixCallback): Unit = fold( { callback.onSuccess(it) }, { callback.onFailure(it) } ) 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 22085e30fc..0a76fb2eef 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 @@ -26,13 +26,13 @@ import org.matrix.android.sdk.api.auth.data.HomeServerConnectionConfig import org.matrix.android.sdk.api.auth.data.SessionParams import org.matrix.android.sdk.api.auth.data.WellKnownBaseConfig import org.matrix.android.sdk.api.legacy.LegacySessionImporter +import org.matrix.android.sdk.api.network.ssl.Fingerprint +import org.matrix.android.sdk.api.util.md5 import org.matrix.android.sdk.internal.auth.SessionParamsStore import org.matrix.android.sdk.internal.crypto.store.db.RealmCryptoStoreMigration import org.matrix.android.sdk.internal.crypto.store.db.RealmCryptoStoreModule import org.matrix.android.sdk.internal.database.RealmKeysUtils import org.matrix.android.sdk.internal.legacy.riot.LoginStorage -import org.matrix.android.sdk.internal.network.ssl.Fingerprint -import org.matrix.android.sdk.internal.util.md5 import timber.log.Timber import java.io.File import javax.inject.Inject diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/legacy/riot/Credentials.java b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/legacy/riot/Credentials.java index 0ca0c7db85..bbed159e3c 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/legacy/riot/Credentials.java +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/legacy/riot/Credentials.java @@ -21,11 +21,9 @@ import org.jetbrains.annotations.Nullable; import org.json.JSONException; import org.json.JSONObject; -/* - * IMPORTANT: This class is imported from Riot-Android to be able to perform a migration. Do not use it for any other purpose - */ - /** + * IMPORTANT: This class is imported from Riot-Android to be able to perform a migration. Do not use it for any other purpose + * * The user's credentials. */ public class Credentials { diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/legacy/riot/Fingerprint.java b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/legacy/riot/Fingerprint.java index 74a3f1ac55..82541d38f6 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/legacy/riot/Fingerprint.java +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/legacy/riot/Fingerprint.java @@ -23,11 +23,9 @@ import org.json.JSONObject; import java.util.Arrays; -/* - * IMPORTANT: This class is imported from Riot-Android to be able to perform a migration. Do not use it for any other purpose - */ - /** + * IMPORTANT: This class is imported from Riot-Android to be able to perform a migration. Do not use it for any other purpose + * * Represents a X509 Certificate fingerprint. */ public class Fingerprint { diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/legacy/riot/HomeServerConnectionConfig.java b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/legacy/riot/HomeServerConnectionConfig.java index 75fc187c45..a1b46f6c09 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/legacy/riot/HomeServerConnectionConfig.java +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/legacy/riot/HomeServerConnectionConfig.java @@ -35,11 +35,9 @@ import okhttp3.CipherSuite; import okhttp3.TlsVersion; import timber.log.Timber; -/* - * IMPORTANT: This class is imported from Riot-Android to be able to perform a migration. Do not use it for any other purpose - */ - /** + * IMPORTANT: This class is imported from Riot-Android to be able to perform a migration. Do not use it for any other purpose + * * Represents how to connect to a specific Homeserver, may include credentials to use. */ public class HomeServerConnectionConfig { diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/legacy/riot/LoginStorage.java b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/legacy/riot/LoginStorage.java index 62f90f563e..924bd461ed 100755 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/legacy/riot/LoginStorage.java +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/legacy/riot/LoginStorage.java @@ -29,11 +29,9 @@ import java.util.List; import timber.log.Timber; -/* - * IMPORTANT: This class is imported from Riot-Android to be able to perform a migration. Do not use it for any other purpose - */ - /** + * IMPORTANT: This class is imported from Riot-Android to be able to perform a migration. Do not use it for any other purpose + * * Stores login credentials in SharedPreferences. */ public class LoginStorage { diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/legacy/riot/WellKnown.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/legacy/riot/WellKnown.kt index 17fd0925f8..3b4bd1b1a3 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/legacy/riot/WellKnown.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/legacy/riot/WellKnown.kt @@ -18,11 +18,9 @@ package org.matrix.android.sdk.internal.legacy.riot import com.squareup.moshi.Json import com.squareup.moshi.JsonClass -/* - * IMPORTANT: This class is imported from Riot-Android to be able to perform a migration. Do not use it for any other purpose - */ - /** + * IMPORTANT: This class is imported from Riot-Android to be able to perform a migration. Do not use it for any other purpose + * * https://matrix.org/docs/spec/client_server/r0.4.0.html#server-discovery *
  * {
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/legacy/riot/WellKnownBaseConfig.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/legacy/riot/WellKnownBaseConfig.kt
index 7bbdda5eaa..2a4ae295fd 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/legacy/riot/WellKnownBaseConfig.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/legacy/riot/WellKnownBaseConfig.kt
@@ -18,11 +18,9 @@ package org.matrix.android.sdk.internal.legacy.riot
 import com.squareup.moshi.Json
 import com.squareup.moshi.JsonClass
 
-/*
- * IMPORTANT: This class is imported from Riot-Android to be able to perform a migration. Do not use it for any other purpose
- */
-
 /**
+ * IMPORTANT: This class is imported from Riot-Android to be able to perform a migration. Do not use it for any other purpose
+ *
  * https://matrix.org/docs/spec/client_server/r0.4.0.html#server-discovery
  * 
  * {
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/legacy/riot/WellKnownManagerConfig.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/legacy/riot/WellKnownManagerConfig.kt
index 4efb52d61c..6b1c67f7cb 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/legacy/riot/WellKnownManagerConfig.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/legacy/riot/WellKnownManagerConfig.kt
@@ -15,10 +15,9 @@
  */
 package org.matrix.android.sdk.internal.legacy.riot
 
-/*
- * IMPORTANT: This class is imported from Riot-Android to be able to perform a migration. Do not use it for any other purpose
+/**
+ * IMPORTANT: This class is imported from Riot-Android to be able to perform a migration. Do not use it for any other purpose
  */
-
 data class WellKnownManagerConfig(
         val apiUrl: String,
         val uiUrl: String
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/legacy/riot/WellKnownPreferredConfig.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/legacy/riot/WellKnownPreferredConfig.kt
index feefdf920d..beb95a1d6f 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/legacy/riot/WellKnownPreferredConfig.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/legacy/riot/WellKnownPreferredConfig.kt
@@ -18,11 +18,9 @@ package org.matrix.android.sdk.internal.legacy.riot
 import com.squareup.moshi.Json
 import com.squareup.moshi.JsonClass
 
-/*
- * IMPORTANT: This class is imported from Riot-Android to be able to perform a migration. Do not use it for any other purpose
- */
-
 /**
+ * IMPORTANT: This class is imported from Riot-Android to be able to perform a migration. Do not use it for any other purpose
+ *
  * https://matrix.org/docs/spec/client_server/r0.4.0.html#server-discovery
  * 
  * {
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/network/HttpHeaders.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/network/HttpHeaders.kt
index 26bdd90507..402e956caa 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/network/HttpHeaders.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/network/HttpHeaders.kt
@@ -16,7 +16,7 @@
 
 package org.matrix.android.sdk.internal.network
 
-object HttpHeaders {
+internal object HttpHeaders {
 
     const val Authorization = "Authorization"
     const val UserAgent = "User-Agent"
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 e32f6be6fc..3d2b2bfbfb 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/network/NetworkConnectivityChecker.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/network/NetworkConnectivityChecker.kt
@@ -25,7 +25,7 @@ import java.util.Collections
 import java.util.concurrent.atomic.AtomicBoolean
 import javax.inject.Inject
 
-interface NetworkConnectivityChecker {
+internal interface NetworkConnectivityChecker {
     /**
      * Returns true when internet is available
      */
@@ -44,7 +44,7 @@ interface NetworkConnectivityChecker {
 internal class DefaultNetworkConnectivityChecker @Inject constructor(private val homeServerPinger: HomeServerPinger,
                                                                      private val backgroundDetectionObserver: BackgroundDetectionObserver,
                                                                      private val networkCallbackStrategy: NetworkCallbackStrategy) :
-    NetworkConnectivityChecker {
+        NetworkConnectivityChecker {
 
     private val hasInternetAccess = AtomicBoolean(true)
     private val listeners = Collections.synchronizedSet(LinkedHashSet())
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/network/RequestExecutor.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/network/RequestExecutor.kt
index 5cd2d88000..71df7c08be 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/network/RequestExecutor.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/network/RequestExecutor.kt
@@ -15,6 +15,7 @@
  */
 
 package org.matrix.android.sdk.internal.network
+
 import org.matrix.android.sdk.internal.network.executeRequest as internalExecuteRequest
 
 internal interface RequestExecutor {
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/network/UnitConverterFactory.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/network/UnitConverterFactory.kt
index 513d8c5c86..f2571ab73f 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
@@ -21,7 +21,7 @@ import retrofit2.Converter
 import retrofit2.Retrofit
 import java.lang.reflect.Type
 
-object UnitConverterFactory : Converter.Factory() {
+internal object UnitConverterFactory : Converter.Factory() {
     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/parsing/CheckNumberType.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/network/parsing/CheckNumberType.kt
index 27684bbf1a..6efa347d3a 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/network/parsing/CheckNumberType.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/network/parsing/CheckNumberType.kt
@@ -28,7 +28,7 @@ import java.math.BigDecimal
 /**
  * This is used to check if NUMBER in json is integer or double, so we can preserve typing when serializing/deserializing in a row.
  */
-interface CheckNumberType {
+internal interface CheckNumberType {
 
     companion object {
         val JSON_ADAPTER_FACTORY = object : JsonAdapter.Factory {
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 f3b4cff34c..628486bb5f 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
@@ -25,7 +25,7 @@ import timber.log.Timber
 @JsonQualifier
 @Retention(AnnotationRetention.RUNTIME)
 @Target(AnnotationTarget.FIELD, AnnotationTarget.VALUE_PARAMETER, AnnotationTarget.FUNCTION)
-annotation class ForceToBoolean
+internal annotation class ForceToBoolean
 
 internal class ForceToBooleanJsonAdapter {
     @ToJson
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/network/parsing/RuntimeJsonAdapterFactory.java b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/network/parsing/RuntimeJsonAdapterFactory.java
deleted file mode 100644
index c9bf6cc662..0000000000
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/network/parsing/RuntimeJsonAdapterFactory.java
+++ /dev/null
@@ -1,169 +0,0 @@
-/*
- * Copyright 2020 The Matrix.org Foundation C.I.C.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package org.matrix.android.sdk.internal.network.parsing;
-
-import com.squareup.moshi.JsonAdapter;
-import com.squareup.moshi.JsonDataException;
-import com.squareup.moshi.JsonReader;
-import com.squareup.moshi.JsonWriter;
-import com.squareup.moshi.Moshi;
-import com.squareup.moshi.Types;
-
-import java.io.IOException;
-import java.lang.annotation.Annotation;
-import java.lang.reflect.Type;
-import java.util.LinkedHashMap;
-import java.util.Map;
-import java.util.Set;
-
-import javax.annotation.CheckReturnValue;
-
-/**
- * A JsonAdapter factory for polymorphic types. This is useful when the type is not known before
- * decoding the JSON. This factory's adapters expect JSON in the format of a JSON object with a
- * key whose value is a label that determines the type to which to map the JSON object.
- */
-public final class RuntimeJsonAdapterFactory implements JsonAdapter.Factory {
-    final Class baseType;
-    final String labelKey;
-    final Class fallbackType;
-    final Map labelToType = new LinkedHashMap<>();
-
-    /**
-     * @param baseType The base type for which this factory will create adapters. Cannot be Object.
-     * @param labelKey The key in the JSON object whose value determines the type to which to map the
-     *                 JSON object.
-     */
-    @CheckReturnValue
-    public static  RuntimeJsonAdapterFactory of(Class baseType, String labelKey, Class fallbackType) {
-        if (baseType == null) throw new NullPointerException("baseType == null");
-        if (labelKey == null) throw new NullPointerException("labelKey == null");
-        if (baseType == Object.class) {
-            throw new IllegalArgumentException(
-                    "The base type must not be Object. Consider using a marker interface.");
-        }
-        return new RuntimeJsonAdapterFactory<>(baseType, labelKey, fallbackType);
-    }
-
-    RuntimeJsonAdapterFactory(Class baseType, String labelKey, Class fallbackType) {
-        this.baseType = baseType;
-        this.labelKey = labelKey;
-        this.fallbackType = fallbackType;
-    }
-
-    /**
-     * Register the subtype that can be created based on the label. When an unknown type is found
-     * during encoding an {@linkplain IllegalArgumentException} will be thrown. When an unknown label
-     * is found during decoding a {@linkplain JsonDataException} will be thrown.
-     */
-    public RuntimeJsonAdapterFactory registerSubtype(Class subtype, String label) {
-        if (subtype == null) throw new NullPointerException("subtype == null");
-        if (label == null) throw new NullPointerException("label == null");
-        if (labelToType.containsKey(label) || labelToType.containsValue(subtype)) {
-            throw new IllegalArgumentException("Subtypes and labels must be unique.");
-        }
-        labelToType.put(label, subtype);
-        return this;
-    }
-
-    @Override
-    public JsonAdapter create(Type type, Set annotations, Moshi moshi) {
-        if (Types.getRawType(type) != baseType || !annotations.isEmpty()) {
-            return null;
-        }
-        int size = labelToType.size();
-        Map> labelToAdapter = new LinkedHashMap<>(size);
-        Map typeToLabel = new LinkedHashMap<>(size);
-        for (Map.Entry entry : labelToType.entrySet()) {
-            String label = entry.getKey();
-            Type typeValue = entry.getValue();
-            typeToLabel.put(typeValue, label);
-            labelToAdapter.put(label, moshi.adapter(typeValue));
-        }
-
-        final JsonAdapter fallbackAdapter = moshi.adapter(fallbackType);
-        JsonAdapter objectJsonAdapter = moshi.adapter(Object.class);
-
-        return new RuntimeJsonAdapter(labelKey, labelToAdapter, typeToLabel,
-                objectJsonAdapter, fallbackAdapter).nullSafe();
-    }
-
-    static final class RuntimeJsonAdapter extends JsonAdapter {
-        final String labelKey;
-        final Map> labelToAdapter;
-        final Map typeToLabel;
-        final JsonAdapter objectJsonAdapter;
-        final JsonAdapter fallbackAdapter;
-
-        RuntimeJsonAdapter(String labelKey, Map> labelToAdapter,
-                           Map typeToLabel, JsonAdapter objectJsonAdapter,
-                           JsonAdapter fallbackAdapter) {
-            this.labelKey = labelKey;
-            this.labelToAdapter = labelToAdapter;
-            this.typeToLabel = typeToLabel;
-            this.objectJsonAdapter = objectJsonAdapter;
-            this.fallbackAdapter = fallbackAdapter;
-        }
-
-        @Override
-        public Object fromJson(JsonReader reader) throws IOException {
-            JsonReader.Token peekedToken = reader.peek();
-            if (peekedToken != JsonReader.Token.BEGIN_OBJECT) {
-                throw new JsonDataException("Expected BEGIN_OBJECT but was " + peekedToken
-                        + " at path " + reader.getPath());
-            }
-            Object jsonValue = reader.readJsonValue();
-            Map jsonObject = (Map) jsonValue;
-            Object label = jsonObject.get(labelKey);
-            if (!(label instanceof String)) {
-                return null;
-            }
-            JsonAdapter adapter = labelToAdapter.get(label);
-            if (adapter == null) {
-                return fallbackAdapter.fromJsonValue(jsonValue);
-            }
-            return adapter.fromJsonValue(jsonValue);
-        }
-
-        @Override
-        public void toJson(JsonWriter writer, Object value) throws IOException {
-            Class type = value.getClass();
-            String label = typeToLabel.get(type);
-            if (label == null) {
-                throw new IllegalArgumentException("Expected one of "
-                        + typeToLabel.keySet()
-                        + " but found "
-                        + value
-                        + ", a "
-                        + value.getClass()
-                        + ". Register this subtype.");
-            }
-            JsonAdapter adapter = labelToAdapter.get(label);
-            Map jsonValue = (Map) adapter.toJsonValue(value);
-
-            Map valueWithLabel = new LinkedHashMap<>(1 + jsonValue.size());
-            valueWithLabel.put(labelKey, label);
-            valueWithLabel.putAll(jsonValue);
-            objectJsonAdapter.toJson(writer, valueWithLabel);
-        }
-
-        @Override
-        public String toString() {
-            return "RuntimeJsonAdapter(" + labelKey + ")";
-        }
-    }
-}
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
new file mode 100644
index 0000000000..0aaa4991cd
--- /dev/null
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/network/parsing/RuntimeJsonAdapterFactory.kt
@@ -0,0 +1,126 @@
+/*
+ * Copyright 2020 The Matrix.org Foundation C.I.C.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.matrix.android.sdk.internal.network.parsing
+
+import com.squareup.moshi.JsonAdapter
+import com.squareup.moshi.JsonDataException
+import com.squareup.moshi.JsonReader
+import com.squareup.moshi.JsonWriter
+import com.squareup.moshi.Moshi
+import com.squareup.moshi.Types
+import java.io.IOException
+import java.lang.reflect.Type
+import javax.annotation.CheckReturnValue
+
+/**
+ * A JsonAdapter factory for polymorphic types. This is useful when the type is not known before
+ * decoding the JSON. This factory's adapters expect JSON in the format of a JSON object with a
+ * key whose value is a label that determines the type to which to map the JSON object.
+ */
+internal class RuntimeJsonAdapterFactory(
+        private val baseType: Class,
+        private val labelKey: String,
+        private val fallbackType: Class<*>
+) : JsonAdapter.Factory {
+    private val labelToType: MutableMap = LinkedHashMap()
+
+    /**
+     * Register the subtype that can be created based on the label. When an unknown type is found
+     * during encoding an [IllegalArgumentException] will be thrown. When an unknown label
+     * is found during decoding a [JsonDataException] will be thrown.
+     */
+    fun registerSubtype(subtype: Class?, label: String?): RuntimeJsonAdapterFactory {
+        if (subtype == null) throw NullPointerException("subtype == null")
+        if (label == null) throw NullPointerException("label == null")
+        require(!(labelToType.containsKey(label) || labelToType.containsValue(subtype))) { "Subtypes and labels must be unique." }
+        labelToType[label] = subtype
+        return this
+    }
+
+    override fun create(type: Type, annotations: Set, moshi: Moshi): JsonAdapter<*>? {
+        if (Types.getRawType(type) != baseType || !annotations.isEmpty()) {
+            return null
+        }
+        val size = labelToType.size
+        val labelToAdapter: MutableMap> = LinkedHashMap(size)
+        val typeToLabel: MutableMap = LinkedHashMap(size)
+        for ((label, typeValue) in labelToType) {
+            typeToLabel[typeValue] = label
+            labelToAdapter[label] = moshi.adapter(typeValue)
+        }
+        val fallbackAdapter = moshi.adapter(fallbackType)
+        val objectJsonAdapter = moshi.adapter(Any::class.java)
+        return RuntimeJsonAdapter(labelKey, labelToAdapter, typeToLabel,
+                objectJsonAdapter, fallbackAdapter).nullSafe()
+    }
+
+    @Suppress("UNCHECKED_CAST")
+    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()
+            if (peekedToken != JsonReader.Token.BEGIN_OBJECT) {
+                throw JsonDataException("Expected BEGIN_OBJECT but was " + peekedToken +
+                        " at path " + reader.path)
+            }
+            val jsonValue = reader.readJsonValue()
+            val jsonObject = jsonValue as Map?
+            val label = jsonObject!![labelKey] as? String ?: return null
+            val adapter = labelToAdapter[label] ?: return fallbackAdapter.fromJsonValue(jsonValue)
+            return adapter.fromJsonValue(jsonValue)
+        }
+
+        @Throws(IOException::class)
+        override fun toJson(writer: JsonWriter, value: Any?) {
+            val type: Class<*> = value!!.javaClass
+            val label = typeToLabel[type]
+                    ?: throw IllegalArgumentException("Expected one of " +
+                            typeToLabel.keys +
+                            " but found " +
+                            value +
+                            ", a " +
+                            value.javaClass +
+                            ". Register this subtype.")
+            val adapter = labelToAdapter[label]!!
+            val jsonValue = adapter.toJsonValue(value) as Map?
+            val valueWithLabel: MutableMap = LinkedHashMap(1 + jsonValue!!.size)
+            valueWithLabel[labelKey] = label
+            valueWithLabel.putAll(jsonValue)
+            objectJsonAdapter.toJson(writer, valueWithLabel)
+        }
+
+        override fun toString(): String {
+            return "RuntimeJsonAdapter($labelKey)"
+        }
+    }
+
+    companion object {
+        /**
+         * @param baseType The base type for which this factory will create adapters. Cannot be Object.
+         * @param labelKey The key in the JSON object whose value determines the type to which to map the
+         * JSON object.
+         */
+        @CheckReturnValue
+        fun  of(baseType: Class, labelKey: String, fallbackType: Class): RuntimeJsonAdapterFactory {
+            require(baseType != Any::class.java) { "The base type must not be Object. Consider using a marker interface." }
+            return RuntimeJsonAdapterFactory(baseType, labelKey, fallbackType)
+        }
+    }
+}
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 6f245aa6d8..ccae5ad14f 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
@@ -16,6 +16,7 @@
 
 package org.matrix.android.sdk.internal.network.ssl
 
+import org.matrix.android.sdk.api.network.ssl.Fingerprint
 import java.security.cert.CertificateException
 import java.security.cert.X509Certificate
 import javax.net.ssl.X509TrustManager
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 4e58a0f2e5..574f1ef81d 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
@@ -18,6 +18,7 @@ package org.matrix.android.sdk.internal.network.ssl
 
 import android.os.Build
 import androidx.annotation.RequiresApi
+import org.matrix.android.sdk.api.network.ssl.Fingerprint
 import java.net.Socket
 import java.security.cert.CertificateException
 import java.security.cert.X509Certificate
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 57b97c75c5..f01ee7af24 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
@@ -17,6 +17,7 @@
 package org.matrix.android.sdk.internal.network.ssl
 
 import android.os.Build
+import org.matrix.android.sdk.api.network.ssl.Fingerprint
 import javax.net.ssl.X509ExtendedTrustManager
 import javax.net.ssl.X509TrustManager
 
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/network/ssl/UnrecognizedCertificateException.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/network/ssl/UnrecognizedCertificateException.kt
index ca841f0ffb..62eb6cf1f6 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/network/ssl/UnrecognizedCertificateException.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/network/ssl/UnrecognizedCertificateException.kt
@@ -16,6 +16,7 @@
 
 package org.matrix.android.sdk.internal.network.ssl
 
+import org.matrix.android.sdk.api.network.ssl.Fingerprint
 import java.security.cert.CertificateException
 import java.security.cert.X509Certificate
 
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/query/QueryEnumListProcessor.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/query/QueryEnumListProcessor.kt
index 5653d7171d..b4415afcbc 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/query/QueryEnumListProcessor.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/query/QueryEnumListProcessor.kt
@@ -19,7 +19,7 @@ package org.matrix.android.sdk.internal.query
 import io.realm.RealmObject
 import io.realm.RealmQuery
 
-fun > RealmQuery.process(field: String, enums: List>): RealmQuery {
+internal fun > RealmQuery.process(field: String, enums: List>): RealmQuery {
     val lastEnumValue = enums.lastOrNull()
     beginGroup()
     for (enumValue in enums) {
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 b42bf2b8c7..ba4d05e747 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
@@ -24,7 +24,7 @@ import org.matrix.android.sdk.api.query.QueryStringValue.ContentQueryStringValue
 import org.matrix.android.sdk.internal.util.Normalizer
 import javax.inject.Inject
 
-class QueryStringValueProcessor @Inject constructor(
+internal class QueryStringValueProcessor @Inject constructor(
         private val normalizer: Normalizer
 ) {
 
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/raw/migration/MigrateGlobalTo001.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/raw/migration/MigrateGlobalTo001.kt
index cff2f7b8e8..7b33257368 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/raw/migration/MigrateGlobalTo001.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/raw/migration/MigrateGlobalTo001.kt
@@ -20,7 +20,7 @@ import io.realm.DynamicRealm
 import org.matrix.android.sdk.internal.database.model.KnownServerUrlEntityFields
 import org.matrix.android.sdk.internal.util.database.RealmMigrator
 
-class MigrateGlobalTo001(realm: DynamicRealm) : RealmMigrator(realm, 1) {
+internal class MigrateGlobalTo001(realm: DynamicRealm) : RealmMigrator(realm, 1) {
 
     override fun doMigrate(realm: DynamicRealm) {
         realm.schema.create("KnownServerUrlEntity")
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 08651764c2..9b83cca558 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
@@ -30,14 +30,14 @@ import okhttp3.RequestBody.Companion.toRequestBody
 import org.matrix.android.sdk.api.MatrixCoroutineDispatchers
 import org.matrix.android.sdk.api.failure.Failure
 import org.matrix.android.sdk.api.session.content.ContentUrlResolver
+import org.matrix.android.sdk.api.session.crypto.attachments.ElementToDecrypt
 import org.matrix.android.sdk.api.session.file.FileService
-import org.matrix.android.sdk.internal.crypto.attachments.ElementToDecrypt
+import org.matrix.android.sdk.api.util.md5
 import org.matrix.android.sdk.internal.crypto.attachments.MXEncryptedAttachments
 import org.matrix.android.sdk.internal.di.SessionDownloadsDirectory
 import org.matrix.android.sdk.internal.di.UnauthenticatedWithCertificateWithProgress
 import org.matrix.android.sdk.internal.session.download.DownloadProgressInterceptor.Companion.DOWNLOAD_PROGRESS_INTERCEPTOR_HEADER
 import org.matrix.android.sdk.internal.util.file.AtomicFileCreator
-import org.matrix.android.sdk.internal.util.md5
 import org.matrix.android.sdk.internal.util.writeToFile
 import timber.log.Timber
 import java.io.File
@@ -123,7 +123,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)
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/DefaultSession.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/DefaultSession.kt
index 1e533158a7..5f77cfb23a 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/DefaultSession.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/DefaultSession.kt
@@ -26,7 +26,6 @@ import org.matrix.android.sdk.api.MatrixCoroutineDispatchers
 import org.matrix.android.sdk.api.auth.data.SessionParams
 import org.matrix.android.sdk.api.failure.GlobalError
 import org.matrix.android.sdk.api.federation.FederationService
-import org.matrix.android.sdk.api.pushrules.PushRuleService
 import org.matrix.android.sdk.api.session.EventStreamService
 import org.matrix.android.sdk.api.session.Session
 import org.matrix.android.sdk.api.session.SessionLifecycleObserver
@@ -53,6 +52,7 @@ import org.matrix.android.sdk.api.session.permalinks.PermalinkService
 import org.matrix.android.sdk.api.session.presence.PresenceService
 import org.matrix.android.sdk.api.session.profile.ProfileService
 import org.matrix.android.sdk.api.session.pushers.PushersService
+import org.matrix.android.sdk.api.session.pushrules.PushRuleService
 import org.matrix.android.sdk.api.session.room.RoomDirectoryService
 import org.matrix.android.sdk.api.session.room.RoomService
 import org.matrix.android.sdk.api.session.search.SearchService
@@ -124,12 +124,12 @@ internal class DefaultSession @Inject constructor(
         private val syncStatusService: Lazy,
         private val homeServerCapabilitiesService: Lazy,
         private val accountDataService: Lazy,
-        private val _sharedSecretStorageService: Lazy,
+        private val sharedSecretStorageService: Lazy,
         private val accountService: Lazy,
         private val eventService: Lazy,
         private val contentScannerService: Lazy,
-        private val identityService: IdentityService,
-        private val integrationManagerService: IntegrationManagerService,
+        private val identityService: Lazy,
+        private val integrationManagerService: Lazy,
         private val thirdPartyService: Lazy,
         private val callSignalingService: Lazy,
         private val spaceService: Lazy,
@@ -140,28 +140,7 @@ internal class DefaultSession @Inject constructor(
         @UnauthenticatedWithCertificate
         private val unauthenticatedWithCertificateOkHttpClient: Lazy
 ) : Session,
-        GlobalErrorHandler.Listener,
-        RoomService by roomService.get(),
-        RoomDirectoryService by roomDirectoryService.get(),
-        GroupService by groupService.get(),
-        UserService by userService.get(),
-        SignOutService by signOutService.get(),
-        FilterService by filterService.get(),
-        PushRuleService by pushRuleService.get(),
-        PushersService by pushersService.get(),
-        EventService by eventService.get(),
-        TermsService by termsService.get(),
-        SyncStatusService by syncStatusService.get(),
-        SecureStorageService by secureStorageService.get(),
-        HomeServerCapabilitiesService by homeServerCapabilitiesService.get(),
-        ProfileService by profileService.get(),
-        PresenceService by presenceService.get(),
-        AccountService by accountService.get(),
-        ToDeviceService by toDeviceService.get(),
-        EventStreamService by eventStreamService.get() {
-
-    override val sharedSecretStorageService: SharedSecretStorageService
-        get() = _sharedSecretStorageService.get()
+        GlobalErrorHandler.Listener {
 
     private var isOpen = false
 
@@ -274,42 +253,44 @@ internal class DefaultSession @Inject constructor(
     }
 
     override fun contentUrlResolver() = contentUrlResolver
-
     override fun contentUploadProgressTracker() = contentUploadProgressTracker
-
     override fun typingUsersTracker() = typingUsersTracker
-
     override fun contentDownloadProgressTracker(): ContentDownloadStateTracker = contentDownloadStateTracker
 
     override fun cryptoService(): CryptoService = cryptoService.get()
-
     override fun contentScannerService(): ContentScannerService = contentScannerService.get()
-
-    override fun identityService() = identityService
-
+    override fun identityService(): IdentityService = identityService.get()
+    override fun homeServerCapabilitiesService(): HomeServerCapabilitiesService = homeServerCapabilitiesService.get()
+    override fun roomService(): RoomService = roomService.get()
+    override fun roomDirectoryService(): RoomDirectoryService = roomDirectoryService.get()
+    override fun groupService(): GroupService = groupService.get()
+    override fun userService(): UserService = userService.get()
+    override fun signOutService(): SignOutService = signOutService.get()
+    override fun filterService(): FilterService = filterService.get()
+    override fun pushRuleService(): PushRuleService = pushRuleService.get()
+    override fun pushersService(): PushersService = pushersService.get()
+    override fun eventService(): EventService = eventService.get()
+    override fun termsService(): TermsService = termsService.get()
+    override fun syncStatusService(): SyncStatusService = syncStatusService.get()
+    override fun secureStorageService(): SecureStorageService = secureStorageService.get()
+    override fun profileService(): ProfileService = profileService.get()
+    override fun presenceService(): PresenceService = presenceService.get()
+    override fun accountService(): AccountService = accountService.get()
+    override fun toDeviceService(): ToDeviceService = toDeviceService.get()
+    override fun eventStreamService(): EventStreamService = eventStreamService.get()
     override fun fileService(): FileService = defaultFileService.get()
-
     override fun permalinkService(): PermalinkService = permalinkService.get()
-
     override fun widgetService(): WidgetService = widgetService.get()
-
     override fun mediaService(): MediaService = mediaService.get()
-
-    override fun integrationManagerService() = integrationManagerService
-
+    override fun integrationManagerService(): IntegrationManagerService = integrationManagerService.get()
     override fun callSignalingService(): CallSignalingService = callSignalingService.get()
-
     override fun searchService(): SearchService = searchService.get()
-
     override fun federationService(): FederationService = federationService.get()
-
     override fun thirdPartyService(): ThirdPartyService = thirdPartyService.get()
-
     override fun spaceService(): SpaceService = spaceService.get()
-
     override fun openIdService(): OpenIdService = openIdService.get()
-
     override fun accountDataService(): SessionAccountDataService = accountDataService.get()
+    override fun sharedSecretStorageService(): SharedSecretStorageService = sharedSecretStorageService.get()
 
     override fun getOkHttpClient(): OkHttpClient {
         return unauthenticatedWithCertificateOkHttpClient.get()
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/DefaultToDeviceService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/DefaultToDeviceService.kt
index 1615b8eef9..609acdd89c 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/DefaultToDeviceService.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/DefaultToDeviceService.kt
@@ -17,10 +17,10 @@
 package org.matrix.android.sdk.internal.session
 
 import org.matrix.android.sdk.api.session.ToDeviceService
+import org.matrix.android.sdk.api.session.crypto.model.MXUsersDevicesMap
 import org.matrix.android.sdk.api.session.events.model.Content
 import org.matrix.android.sdk.api.session.events.model.EventType
 import org.matrix.android.sdk.internal.crypto.actions.MessageEncrypter
-import org.matrix.android.sdk.internal.crypto.model.MXUsersDevicesMap
 import org.matrix.android.sdk.internal.crypto.store.IMXCryptoStore
 import org.matrix.android.sdk.internal.crypto.tasks.SendToDeviceTask
 import javax.inject.Inject
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 531dea1d5a..0aae9f3105 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
@@ -45,6 +45,7 @@ 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
 import org.matrix.android.sdk.internal.crypto.secrets.DefaultSharedSecretStorageService
 import org.matrix.android.sdk.internal.crypto.tasks.DefaultRedactEventTask
 import org.matrix.android.sdk.internal.crypto.tasks.RedactEventTask
@@ -87,6 +88,8 @@ import org.matrix.android.sdk.internal.session.integrationmanager.IntegrationMan
 import org.matrix.android.sdk.internal.session.openid.DefaultOpenIdService
 import org.matrix.android.sdk.internal.session.permalinks.DefaultPermalinkService
 import org.matrix.android.sdk.internal.session.room.EventRelationsAggregationProcessor
+import org.matrix.android.sdk.internal.session.room.aggregation.livelocation.DefaultLiveLocationAggregationProcessor
+import org.matrix.android.sdk.internal.session.room.aggregation.livelocation.LiveLocationAggregationProcessor
 import org.matrix.android.sdk.internal.session.room.create.RoomCreateEventProcessor
 import org.matrix.android.sdk.internal.session.room.prune.RedactionEventProcessor
 import org.matrix.android.sdk.internal.session.room.send.queue.EventSenderProcessor
@@ -96,7 +99,6 @@ import org.matrix.android.sdk.internal.session.securestorage.DefaultSecureStorag
 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
-import org.matrix.android.sdk.internal.util.md5
 import retrofit2.Retrofit
 import java.io.File
 import javax.inject.Provider
@@ -104,7 +106,7 @@ import javax.inject.Qualifier
 
 @Qualifier
 @Retention(AnnotationRetention.RUNTIME)
-annotation class MockHttpInterceptor
+internal annotation class MockHttpInterceptor
 
 @Module
 internal abstract class SessionModule {
@@ -390,4 +392,7 @@ internal abstract class SessionModule {
 
     @Binds
     abstract fun bindEventSenderProcessor(processor: EventSenderProcessorCoroutine): EventSenderProcessor
+
+    @Binds
+    abstract fun bindLiveLocationAggregationProcessor(processor: DefaultLiveLocationAggregationProcessor): LiveLocationAggregationProcessor
 }
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/StreamEventsManager.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/StreamEventsManager.kt
index bb0ca11445..cfc26045a0 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/StreamEventsManager.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/StreamEventsManager.kt
@@ -22,8 +22,8 @@ import kotlinx.coroutines.SupervisorJob
 import kotlinx.coroutines.launch
 import org.matrix.android.sdk.api.extensions.tryOrNull
 import org.matrix.android.sdk.api.session.LiveEventListener
+import org.matrix.android.sdk.api.session.crypto.model.MXEventDecryptionResult
 import org.matrix.android.sdk.api.session.events.model.Event
-import org.matrix.android.sdk.internal.crypto.MXEventDecryptionResult
 import timber.log.Timber
 import javax.inject.Inject
 
@@ -71,7 +71,7 @@ internal class StreamEventsManager @Inject constructor() {
         coroutineScope.launch {
             listeners.forEach {
                 tryOrNull {
-                    it.onEventDecrypted(event.eventId ?: "", event.roomId ?: "", result.clearEvent)
+                    it.onEventDecrypted(event, result.clearEvent)
                 }
             }
         }
@@ -82,7 +82,7 @@ internal class StreamEventsManager @Inject constructor() {
         coroutineScope.launch {
             listeners.forEach {
                 tryOrNull {
-                    it.onEventDecryptionError(event.eventId ?: "", event.roomId ?: "", error)
+                    it.onEventDecryptionError(event, error)
                 }
             }
         }
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/TestInterceptor.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/TestInterceptor.kt
index fad68afd8a..5b2ba91ba2 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/TestInterceptor.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/TestInterceptor.kt
@@ -18,6 +18,6 @@ package org.matrix.android.sdk.internal.session
 
 import okhttp3.Interceptor
 
-interface TestInterceptor : Interceptor {
+internal interface TestInterceptor : Interceptor {
     var sessionId: String?
 }
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/cache/DefaultCacheService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/cache/DefaultCacheService.kt
index 6d0cd37e1f..93b0dba13e 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/cache/DefaultCacheService.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/cache/DefaultCacheService.kt
@@ -21,9 +21,9 @@ import org.matrix.android.sdk.internal.di.SessionDatabase
 import org.matrix.android.sdk.internal.task.TaskExecutor
 import javax.inject.Inject
 
-internal class DefaultCacheService @Inject constructor(@SessionDatabase
-                                                       private val clearCacheTask: ClearCacheTask,
-                                                       private val taskExecutor: TaskExecutor
+internal class DefaultCacheService @Inject constructor(
+        @SessionDatabase private val clearCacheTask: ClearCacheTask,
+        private val taskExecutor: TaskExecutor
 ) : CacheService {
 
     override suspend fun clearCache() {
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/call/CallEventProcessor.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/call/CallEventProcessor.kt
index 3f199c5cce..b15a647421 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/call/CallEventProcessor.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/call/CallEventProcessor.kt
@@ -30,7 +30,7 @@ private val loggerTag = LoggerTag("CallEventProcessor", LoggerTag.VOIP)
 
 @SessionScope
 internal class CallEventProcessor @Inject constructor(private val callSignalingHandler: CallSignalingHandler) :
-    EventInsertLiveProcessor {
+        EventInsertLiveProcessor {
 
     private val allowedTypes = listOf(
             EventType.CALL_ANSWER,
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/content/DefaultContentUrlResolver.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/content/DefaultContentUrlResolver.kt
index 660ab8726f..5d77424482 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
@@ -21,7 +21,7 @@ import org.matrix.android.sdk.api.MatrixUrls.removeMxcPrefix
 import org.matrix.android.sdk.api.auth.data.HomeServerConnectionConfig
 import org.matrix.android.sdk.api.session.content.ContentUrlResolver
 import org.matrix.android.sdk.api.session.contentscanner.ContentScannerService
-import org.matrix.android.sdk.internal.crypto.attachments.ElementToDecrypt
+import org.matrix.android.sdk.api.session.crypto.attachments.ElementToDecrypt
 import org.matrix.android.sdk.internal.network.NetworkConstants
 import org.matrix.android.sdk.internal.session.contentscanner.ScanEncryptorUtils
 import org.matrix.android.sdk.internal.session.contentscanner.model.toJson
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/content/FileUploader.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/content/FileUploader.kt
index e9cb423893..f96a019fe2 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/content/FileUploader.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/content/FileUploader.kt
@@ -148,8 +148,8 @@ internal class FileUploader @Inject constructor(
                 .post(requestBody)
                 .build()
 
-       return withContext(coroutineDispatchers.io) {
-             okHttpClient.newCall(request).awaitResponse().use { response ->
+        return withContext(coroutineDispatchers.io) {
+            okHttpClient.newCall(request).awaitResponse().use { response ->
                 if (!response.isSuccessful) {
                     throw response.toFailure(globalErrorReceiver)
                 } else {
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/content/ImageCompressor.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/content/ImageCompressor.kt
index 01eb52ff22..c5aa6cd5e7 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/content/ImageCompressor.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/content/ImageCompressor.kt
@@ -68,16 +68,16 @@ internal class ImageCompressor @Inject constructor(
                     val orientation = exifInfo.getAttributeInt(ExifInterface.TAG_ORIENTATION, ExifInterface.ORIENTATION_NORMAL)
                     val matrix = Matrix()
                     when (orientation) {
-                        ExifInterface.ORIENTATION_ROTATE_270 -> matrix.postRotate(270f)
-                        ExifInterface.ORIENTATION_ROTATE_180 -> matrix.postRotate(180f)
-                        ExifInterface.ORIENTATION_ROTATE_90 -> matrix.postRotate(90f)
+                        ExifInterface.ORIENTATION_ROTATE_270      -> matrix.postRotate(270f)
+                        ExifInterface.ORIENTATION_ROTATE_180      -> matrix.postRotate(180f)
+                        ExifInterface.ORIENTATION_ROTATE_90       -> matrix.postRotate(90f)
                         ExifInterface.ORIENTATION_FLIP_HORIZONTAL -> matrix.preScale(-1f, 1f)
-                        ExifInterface.ORIENTATION_FLIP_VERTICAL -> matrix.preScale(1f, -1f)
-                        ExifInterface.ORIENTATION_TRANSPOSE -> {
+                        ExifInterface.ORIENTATION_FLIP_VERTICAL   -> matrix.preScale(1f, -1f)
+                        ExifInterface.ORIENTATION_TRANSPOSE       -> {
                             matrix.preRotate(-90f)
                             matrix.preScale(-1f, 1f)
                         }
-                        ExifInterface.ORIENTATION_TRANSVERSE -> {
+                        ExifInterface.ORIENTATION_TRANSVERSE      -> {
                             matrix.preRotate(90f)
                             matrix.preScale(-1f, 1f)
                         }
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/content/UploadContentWorker.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/content/UploadContentWorker.kt
index 52dee0ee55..75606f2e7a 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
@@ -25,6 +25,7 @@ import com.squareup.moshi.JsonClass
 import org.matrix.android.sdk.api.extensions.tryOrNull
 import org.matrix.android.sdk.api.listeners.ProgressListener
 import org.matrix.android.sdk.api.session.content.ContentAttachmentData
+import org.matrix.android.sdk.api.session.crypto.model.EncryptedFileInfo
 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.MessageAudioContent
@@ -35,7 +36,6 @@ import org.matrix.android.sdk.api.session.room.model.message.MessageVideoContent
 import org.matrix.android.sdk.api.util.MimeTypes
 import org.matrix.android.sdk.internal.SessionManager
 import org.matrix.android.sdk.internal.crypto.attachments.MXEncryptedAttachments
-import org.matrix.android.sdk.internal.crypto.model.rest.EncryptedFileInfo
 import org.matrix.android.sdk.internal.database.mapper.ContentMapper
 import org.matrix.android.sdk.internal.database.mapper.asDomain
 import org.matrix.android.sdk.internal.network.ProgressRequestBody
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/contentscanner/DefaultContentScannerService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/contentscanner/DefaultContentScannerService.kt
index 4ecb337603..da7e2d102e 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/contentscanner/DefaultContentScannerService.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/contentscanner/DefaultContentScannerService.kt
@@ -23,8 +23,8 @@ import okhttp3.OkHttpClient
 import org.matrix.android.sdk.api.session.contentscanner.ContentScannerService
 import org.matrix.android.sdk.api.session.contentscanner.ScanState
 import org.matrix.android.sdk.api.session.contentscanner.ScanStatusInfo
+import org.matrix.android.sdk.api.session.crypto.attachments.ElementToDecrypt
 import org.matrix.android.sdk.api.util.Optional
-import org.matrix.android.sdk.internal.crypto.attachments.ElementToDecrypt
 import org.matrix.android.sdk.internal.di.Unauthenticated
 import org.matrix.android.sdk.internal.network.RetrofitFactory
 import org.matrix.android.sdk.internal.session.SessionScope
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/contentscanner/DisabledContentScannerService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/contentscanner/DisabledContentScannerService.kt
index 9087c71566..41c444ad83 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/contentscanner/DisabledContentScannerService.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/contentscanner/DisabledContentScannerService.kt
@@ -20,8 +20,8 @@ import androidx.lifecycle.LiveData
 import androidx.lifecycle.MutableLiveData
 import org.matrix.android.sdk.api.session.contentscanner.ContentScannerService
 import org.matrix.android.sdk.api.session.contentscanner.ScanStatusInfo
+import org.matrix.android.sdk.api.session.crypto.attachments.ElementToDecrypt
 import org.matrix.android.sdk.api.util.Optional
-import org.matrix.android.sdk.internal.crypto.attachments.ElementToDecrypt
 import org.matrix.android.sdk.internal.session.SessionScope
 import javax.inject.Inject
 
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/contentscanner/ScanEncryptorUtils.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/contentscanner/ScanEncryptorUtils.kt
index 8fc84a487e..7d14e4ed80 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/contentscanner/ScanEncryptorUtils.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/contentscanner/ScanEncryptorUtils.kt
@@ -16,9 +16,9 @@
 
 package org.matrix.android.sdk.internal.session.contentscanner
 
-import org.matrix.android.sdk.internal.crypto.attachments.ElementToDecrypt
-import org.matrix.android.sdk.internal.crypto.model.rest.EncryptedFileInfo
-import org.matrix.android.sdk.internal.crypto.model.rest.EncryptedFileKey
+import org.matrix.android.sdk.api.session.crypto.attachments.ElementToDecrypt
+import org.matrix.android.sdk.api.session.crypto.model.EncryptedFileInfo
+import org.matrix.android.sdk.api.session.crypto.model.EncryptedFileKey
 import org.matrix.android.sdk.internal.crypto.tools.withOlmEncryption
 import org.matrix.android.sdk.internal.session.contentscanner.model.DownloadBody
 import org.matrix.android.sdk.internal.session.contentscanner.model.EncryptedBody
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/contentscanner/model/DownloadBody.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/contentscanner/model/DownloadBody.kt
index 5bac96a0c0..5ffb4e7983 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/contentscanner/model/DownloadBody.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/contentscanner/model/DownloadBody.kt
@@ -18,7 +18,7 @@ package org.matrix.android.sdk.internal.session.contentscanner.model
 
 import com.squareup.moshi.Json
 import com.squareup.moshi.JsonClass
-import org.matrix.android.sdk.internal.crypto.model.rest.EncryptedFileInfo
+import org.matrix.android.sdk.api.session.crypto.model.EncryptedFileInfo
 import org.matrix.android.sdk.internal.di.MoshiProvider
 import org.matrix.android.sdk.internal.util.JsonCanonicalizer
 
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/contentscanner/tasks/DownloadEncryptedTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/contentscanner/tasks/DownloadEncryptedTask.kt
index f92c869cb8..abde84b6af 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/contentscanner/tasks/DownloadEncryptedTask.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/contentscanner/tasks/DownloadEncryptedTask.kt
@@ -17,7 +17,7 @@
 package org.matrix.android.sdk.internal.session.contentscanner.tasks
 
 import okhttp3.ResponseBody
-import org.matrix.android.sdk.internal.crypto.attachments.ElementToDecrypt
+import org.matrix.android.sdk.api.session.crypto.attachments.ElementToDecrypt
 import org.matrix.android.sdk.internal.network.executeRequest
 import org.matrix.android.sdk.internal.session.contentscanner.ContentScannerApiProvider
 import org.matrix.android.sdk.internal.session.contentscanner.ScanEncryptorUtils
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/contentscanner/tasks/ScanEncryptedTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/contentscanner/tasks/ScanEncryptedTask.kt
index dab9b5538f..e098607eb6 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/contentscanner/tasks/ScanEncryptedTask.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/contentscanner/tasks/ScanEncryptedTask.kt
@@ -18,7 +18,7 @@ package org.matrix.android.sdk.internal.session.contentscanner.tasks
 
 import org.matrix.android.sdk.api.failure.toScanFailure
 import org.matrix.android.sdk.api.session.contentscanner.ScanState
-import org.matrix.android.sdk.internal.crypto.attachments.ElementToDecrypt
+import org.matrix.android.sdk.api.session.crypto.attachments.ElementToDecrypt
 import org.matrix.android.sdk.internal.network.executeRequest
 import org.matrix.android.sdk.internal.session.contentscanner.ContentScannerApiProvider
 import org.matrix.android.sdk.internal.session.contentscanner.ScanEncryptorUtils
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 19bc7e1908..16c57baafc 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
@@ -16,9 +16,9 @@
 
 package org.matrix.android.sdk.internal.session.directory
 
+import org.matrix.android.sdk.api.session.room.alias.RoomAliasDescription
 import org.matrix.android.sdk.internal.network.NetworkConstants
 import org.matrix.android.sdk.internal.session.room.alias.AddRoomAliasBody
-import org.matrix.android.sdk.internal.session.room.alias.RoomAliasDescription
 import retrofit2.http.Body
 import retrofit2.http.DELETE
 import retrofit2.http.GET
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 f4cb1a80e5..4fd4fda7d1 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
@@ -24,7 +24,7 @@ import okio.ForwardingSource
 import okio.Source
 import okio.buffer
 
-class ProgressResponseBody(
+internal class ProgressResponseBody(
         private val responseBody: ResponseBody,
         private val chainUrl: String,
         private val progressListener: ProgressListener) : ResponseBody() {
@@ -56,7 +56,7 @@ class ProgressResponseBody(
     }
 }
 
-interface ProgressListener {
+internal interface ProgressListener {
     fun update(url: String, bytesRead: Long, contentLength: Long, done: Boolean)
     fun error(url: String, errorCode: Int)
 }
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/filter/EventFilter.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/filter/EventFilter.kt
index 37630ef8ba..27a12a6145 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/filter/EventFilter.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/filter/EventFilter.kt
@@ -23,7 +23,7 @@ import com.squareup.moshi.JsonClass
  * https://matrix.org/docs/spec/client_server/r0.3.0.html#post-matrix-client-r0-user-userid-filter
  */
 @JsonClass(generateAdapter = true)
-data class EventFilter(
+internal data class EventFilter(
         /**
          * The maximum number of events to return.
          */
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/filter/FilterResponse.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/filter/FilterResponse.kt
index b2d5429216..3719c803cc 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/filter/FilterResponse.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/filter/FilterResponse.kt
@@ -23,7 +23,7 @@ import com.squareup.moshi.JsonClass
  * https://matrix.org/docs/spec/client_server/r0.3.0.html#post-matrix-client-r0-user-userid-filter
  */
 @JsonClass(generateAdapter = true)
-data class FilterResponse(
+internal data class FilterResponse(
         /**
          * Required. The ID of the filter that was created. Cannot start with a { as this character
          * is used to determine if the filter provided is inline JSON or a previously declared
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/filter/RoomEventFilter.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/filter/RoomEventFilter.kt
index 634ea73480..220c401137 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/filter/RoomEventFilter.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/filter/RoomEventFilter.kt
@@ -24,7 +24,7 @@ import org.matrix.android.sdk.internal.di.MoshiProvider
  * https://matrix.org/docs/spec/client_server/r0.3.0.html#post-matrix-client-r0-user-userid-filter
  */
 @JsonClass(generateAdapter = true)
-data class RoomEventFilter(
+internal data class RoomEventFilter(
         /**
          * The maximum number of events to return.
          */
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/filter/RoomFilter.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/filter/RoomFilter.kt
index 2c56a30d39..585d013eae 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/filter/RoomFilter.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/filter/RoomFilter.kt
@@ -23,7 +23,7 @@ import com.squareup.moshi.JsonClass
  * https://matrix.org/docs/spec/client_server/r0.3.0.html#post-matrix-client-r0-user-userid-filter
  */
 @JsonClass(generateAdapter = true)
-data class RoomFilter(
+internal data class RoomFilter(
         /**
          * A list of room IDs to exclude. If this list is absent then no rooms are excluded.
          * A matching room will be excluded even if it is listed in the 'rooms' filter.
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 c8a9c0f09a..4285f38893 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
@@ -39,13 +39,13 @@ import org.matrix.android.sdk.api.session.identity.IdentityServiceError
 import org.matrix.android.sdk.api.session.identity.IdentityServiceListener
 import org.matrix.android.sdk.api.session.identity.SharedState
 import org.matrix.android.sdk.api.session.identity.ThreePid
+import org.matrix.android.sdk.api.session.identity.model.SignInvitationResult
 import org.matrix.android.sdk.internal.di.AuthenticatedIdentity
 import org.matrix.android.sdk.internal.di.UnauthenticatedWithCertificate
 import org.matrix.android.sdk.internal.extensions.observeNotNull
 import org.matrix.android.sdk.internal.network.RetrofitFactory
 import org.matrix.android.sdk.internal.session.SessionScope
 import org.matrix.android.sdk.internal.session.identity.data.IdentityStore
-import org.matrix.android.sdk.internal.session.identity.model.SignInvitationResult
 import org.matrix.android.sdk.internal.session.openid.GetOpenIdTokenTask
 import org.matrix.android.sdk.internal.session.profile.BindThreePidsTask
 import org.matrix.android.sdk.internal.session.profile.UnbindThreePidsTask
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 99bd740463..7ca8758677 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
@@ -16,6 +16,7 @@
 
 package org.matrix.android.sdk.internal.session.identity
 
+import org.matrix.android.sdk.api.session.identity.model.SignInvitationResult
 import org.matrix.android.sdk.internal.auth.registration.SuccessResult
 import org.matrix.android.sdk.internal.network.NetworkConstants
 import org.matrix.android.sdk.internal.session.identity.model.IdentityAccountResponse
@@ -26,7 +27,6 @@ import org.matrix.android.sdk.internal.session.identity.model.IdentityRequestOwn
 import org.matrix.android.sdk.internal.session.identity.model.IdentityRequestTokenForEmailBody
 import org.matrix.android.sdk.internal.session.identity.model.IdentityRequestTokenForMsisdnBody
 import org.matrix.android.sdk.internal.session.identity.model.IdentityRequestTokenResponse
-import org.matrix.android.sdk.internal.session.identity.model.SignInvitationResult
 import retrofit2.http.Body
 import retrofit2.http.GET
 import retrofit2.http.POST
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/identity/Sign3pidInvitationTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/identity/Sign3pidInvitationTask.kt
index d491af33ca..06a6cecc05 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/identity/Sign3pidInvitationTask.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/identity/Sign3pidInvitationTask.kt
@@ -18,10 +18,10 @@ package org.matrix.android.sdk.internal.session.identity
 
 import dagger.Lazy
 import okhttp3.OkHttpClient
+import org.matrix.android.sdk.api.session.identity.model.SignInvitationResult
 import org.matrix.android.sdk.internal.di.Unauthenticated
 import org.matrix.android.sdk.internal.di.UserId
 import org.matrix.android.sdk.internal.network.RetrofitFactory
-import org.matrix.android.sdk.internal.session.identity.model.SignInvitationResult
 import org.matrix.android.sdk.internal.task.Task
 import javax.inject.Inject
 
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/identity/db/migration/MigrateIdentityTo001.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/identity/db/migration/MigrateIdentityTo001.kt
index 002601470d..17a23b828a 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/identity/db/migration/MigrateIdentityTo001.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/identity/db/migration/MigrateIdentityTo001.kt
@@ -21,7 +21,7 @@ import org.matrix.android.sdk.internal.session.identity.db.IdentityDataEntityFie
 import org.matrix.android.sdk.internal.util.database.RealmMigrator
 import timber.log.Timber
 
-class MigrateIdentityTo001(realm: DynamicRealm) : RealmMigrator(realm, 1) {
+internal class MigrateIdentityTo001(realm: DynamicRealm) : RealmMigrator(realm, 1) {
 
     override fun doMigrate(realm: DynamicRealm) {
         Timber.d("Add field userConsent (Boolean) and set the value to false")
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/initsync/DefaultSyncStatusService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/initsync/DefaultSyncStatusService.kt
index 079b0d0115..c138c1a40e 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/initsync/DefaultSyncStatusService.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/initsync/DefaultSyncStatusService.kt
@@ -24,7 +24,7 @@ import javax.inject.Inject
 
 @SessionScope
 internal class DefaultSyncStatusService @Inject constructor() :
-    SyncStatusService,
+        SyncStatusService,
         ProgressReporter {
 
     private val status = MutableLiveData()
@@ -72,7 +72,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.Progressing(leaf.initSyncStep, root.currentProgress.toInt()))
+                status.postValue(SyncStatusService.Status.InitialSyncProgressing(leaf.initSyncStep, root.currentProgress.toInt()))
             }
         }
     }
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/integrationmanager/IntegrationManager.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/integrationmanager/IntegrationManager.kt
index 30b1589169..1b96931c6c 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/integrationmanager/IntegrationManager.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/integrationmanager/IntegrationManager.kt
@@ -59,7 +59,7 @@ internal class IntegrationManager @Inject constructor(matrixConfiguration: Matri
                                                       private val updateUserAccountDataTask: UpdateUserAccountDataTask,
                                                       private val accountDataDataSource: UserAccountDataDataSource,
                                                       private val widgetFactory: WidgetFactory) :
-    SessionLifecycleObserver {
+        SessionLifecycleObserver {
 
     private val currentConfigs = ArrayList()
     private val lifecycleOwner: LifecycleOwner = LifecycleOwner { lifecycleRegistry }
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/presence/model/GetPresenceResponse.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/presence/model/GetPresenceResponse.kt
index a7552f7b02..1cd6d7d3f2 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/presence/model/GetPresenceResponse.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/presence/model/GetPresenceResponse.kt
@@ -21,7 +21,7 @@ import com.squareup.moshi.JsonClass
 import org.matrix.android.sdk.api.session.presence.model.PresenceEnum
 
 @JsonClass(generateAdapter = true)
-data class GetPresenceResponse(
+internal data class GetPresenceResponse(
         @Json(name = "presence")
         val presence: PresenceEnum,
         @Json(name = "last_active_ago")
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/presence/model/PresenceContent.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/presence/model/PresenceContent.kt
index 45e0fcf06e..b1ca512652 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/presence/model/PresenceContent.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/presence/model/PresenceContent.kt
@@ -24,7 +24,7 @@ import org.matrix.android.sdk.api.session.presence.model.PresenceEnum
  * Class representing the EventType.PRESENCE event content
  */
 @JsonClass(generateAdapter = true)
-data class PresenceContent(
+internal data class PresenceContent(
         /**
          * Required. The presence state for this user. One of: ["online", "offline", "unavailable"]
          */
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/presence/service/DefaultPresenceService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/presence/service/DefaultPresenceService.kt
index 1083d5b4c2..ca89ef684f 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/presence/service/DefaultPresenceService.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/presence/service/DefaultPresenceService.kt
@@ -23,15 +23,19 @@ import org.matrix.android.sdk.api.session.presence.model.UserPresence
 import org.matrix.android.sdk.internal.di.UserId
 import org.matrix.android.sdk.internal.session.presence.service.task.GetPresenceTask
 import org.matrix.android.sdk.internal.session.presence.service.task.SetPresenceTask
+import org.matrix.android.sdk.internal.session.sync.SyncPresence
+import org.matrix.android.sdk.internal.settings.DefaultLightweightSettingsStorage
 import javax.inject.Inject
 
 internal class DefaultPresenceService @Inject constructor(
         @UserId private val userId: String,
         private val setPresenceTask: SetPresenceTask,
-        private val getPresenceTask: GetPresenceTask
+        private val getPresenceTask: GetPresenceTask,
+        private val lightweightSettingsStorage: DefaultLightweightSettingsStorage
 ) : PresenceService {
 
     override suspend fun setMyPresence(presence: PresenceEnum, statusMsg: String?) {
+        lightweightSettingsStorage.setSyncPresenceStatus(SyncPresence.from(presence))
         setPresenceTask.execute(SetPresenceTask.Params(userId, presence, statusMsg))
     }
 
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/profile/BindThreePidBody.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/profile/BindThreePidBody.kt
index fa45ae9940..4d2a999137 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/profile/BindThreePidBody.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/profile/BindThreePidBody.kt
@@ -30,17 +30,17 @@ internal data class BindThreePidBody(
          * Required. The identity server to use. (without "https://")
          */
         @Json(name = "id_server")
-        var identityServerUrlWithoutProtocol: String,
+        val identityServerUrlWithoutProtocol: String,
 
         /**
          * Required. An access token previously registered with the identity server.
          */
         @Json(name = "id_access_token")
-        var identityServerAccessToken: String,
+        val identityServerAccessToken: String,
 
         /**
          * Required. The session identifier given by the identity server.
          */
         @Json(name = "sid")
-        var sid: String
+        val sid: String
 )
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/pushers/AddPushRuleTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/pushers/AddPushRuleTask.kt
index b217687168..c46474cf90 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/pushers/AddPushRuleTask.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/pushers/AddPushRuleTask.kt
@@ -15,8 +15,8 @@
  */
 package org.matrix.android.sdk.internal.session.pushers
 
-import org.matrix.android.sdk.api.pushrules.RuleKind
-import org.matrix.android.sdk.api.pushrules.rest.PushRule
+import org.matrix.android.sdk.api.session.pushrules.RuleKind
+import org.matrix.android.sdk.api.session.pushrules.rest.PushRule
 import org.matrix.android.sdk.internal.network.GlobalErrorReceiver
 import org.matrix.android.sdk.internal.network.executeRequest
 import org.matrix.android.sdk.internal.task.Task
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/pushers/AddPusherWorker.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/pushers/AddPusherWorker.kt
index ce29efaaac..0042558027 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/pushers/AddPusherWorker.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/pushers/AddPusherWorker.kt
@@ -26,7 +26,7 @@ import org.matrix.android.sdk.internal.worker.SessionWorkerParams
 import javax.inject.Inject
 
 internal class AddPusherWorker(context: Context, params: WorkerParameters, sessionManager: SessionManager) :
-    SessionSafeCoroutineWorker(context, params, sessionManager, Params::class.java) {
+        SessionSafeCoroutineWorker(context, params, sessionManager, Params::class.java) {
 
     @JsonClass(generateAdapter = true)
     internal data class Params(
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/pushers/DefaultConditionResolver.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/pushers/DefaultConditionResolver.kt
index 84a05067be..0aa58b7410 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/pushers/DefaultConditionResolver.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/pushers/DefaultConditionResolver.kt
@@ -15,14 +15,14 @@
  */
 package org.matrix.android.sdk.internal.session.pushers
 
-import org.matrix.android.sdk.api.pushrules.ConditionResolver
-import org.matrix.android.sdk.api.pushrules.ContainsDisplayNameCondition
-import org.matrix.android.sdk.api.pushrules.EventMatchCondition
-import org.matrix.android.sdk.api.pushrules.RoomMemberCountCondition
-import org.matrix.android.sdk.api.pushrules.SenderNotificationPermissionCondition
 import org.matrix.android.sdk.api.session.events.model.Event
 import org.matrix.android.sdk.api.session.events.model.EventType
 import org.matrix.android.sdk.api.session.events.model.toModel
+import org.matrix.android.sdk.api.session.pushrules.ConditionResolver
+import org.matrix.android.sdk.api.session.pushrules.ContainsDisplayNameCondition
+import org.matrix.android.sdk.api.session.pushrules.EventMatchCondition
+import org.matrix.android.sdk.api.session.pushrules.RoomMemberCountCondition
+import org.matrix.android.sdk.api.session.pushrules.SenderNotificationPermissionCondition
 import org.matrix.android.sdk.api.session.room.model.PowerLevelsContent
 import org.matrix.android.sdk.internal.di.UserId
 import org.matrix.android.sdk.internal.session.room.RoomGetter
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/pushrules/rest/GetPushRulesResponse.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/pushers/GetPushRulesResponse.kt
similarity index 90%
rename from matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/pushrules/rest/GetPushRulesResponse.kt
rename to matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/pushers/GetPushRulesResponse.kt
index 35b4d77c0e..de03819629 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/pushrules/rest/GetPushRulesResponse.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/pushers/GetPushRulesResponse.kt
@@ -13,10 +13,11 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package org.matrix.android.sdk.api.pushrules.rest
+package org.matrix.android.sdk.internal.session.pushers
 
 import com.squareup.moshi.Json
 import com.squareup.moshi.JsonClass
+import org.matrix.android.sdk.api.session.pushrules.rest.RuleSet
 
 /**
  * All push rulesets for a user.
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/pushers/PushRulesApi.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/pushers/PushRulesApi.kt
index 994b4860c6..dab6d37317 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/pushers/PushRulesApi.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/pushers/PushRulesApi.kt
@@ -15,8 +15,7 @@
  */
 package org.matrix.android.sdk.internal.session.pushers
 
-import org.matrix.android.sdk.api.pushrules.rest.GetPushRulesResponse
-import org.matrix.android.sdk.api.pushrules.rest.PushRule
+import org.matrix.android.sdk.api.session.pushrules.rest.PushRule
 import org.matrix.android.sdk.internal.network.NetworkConstants
 import retrofit2.http.Body
 import retrofit2.http.DELETE
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/pushers/PushersModule.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/pushers/PushersModule.kt
index d53a4eed65..4528c95e69 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/pushers/PushersModule.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/pushers/PushersModule.kt
@@ -19,14 +19,14 @@ package org.matrix.android.sdk.internal.session.pushers
 import dagger.Binds
 import dagger.Module
 import dagger.Provides
-import org.matrix.android.sdk.api.pushrules.ConditionResolver
-import org.matrix.android.sdk.api.pushrules.PushRuleService
 import org.matrix.android.sdk.api.session.pushers.PushersService
-import org.matrix.android.sdk.internal.session.notification.DefaultProcessEventForPushTask
-import org.matrix.android.sdk.internal.session.notification.DefaultPushRuleService
-import org.matrix.android.sdk.internal.session.notification.ProcessEventForPushTask
+import org.matrix.android.sdk.api.session.pushrules.ConditionResolver
+import org.matrix.android.sdk.api.session.pushrules.PushRuleService
 import org.matrix.android.sdk.internal.session.pushers.gateway.DefaultPushGatewayNotifyTask
 import org.matrix.android.sdk.internal.session.pushers.gateway.PushGatewayNotifyTask
+import org.matrix.android.sdk.internal.session.pushrules.DefaultProcessEventForPushTask
+import org.matrix.android.sdk.internal.session.pushrules.DefaultPushRuleService
+import org.matrix.android.sdk.internal.session.pushrules.ProcessEventForPushTask
 import org.matrix.android.sdk.internal.session.room.notification.DefaultSetRoomNotificationStateTask
 import org.matrix.android.sdk.internal.session.room.notification.SetRoomNotificationStateTask
 import retrofit2.Retrofit
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/pushers/RemovePushRuleTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/pushers/RemovePushRuleTask.kt
index bae893608b..9b0bf7934b 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/pushers/RemovePushRuleTask.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/pushers/RemovePushRuleTask.kt
@@ -15,7 +15,7 @@
  */
 package org.matrix.android.sdk.internal.session.pushers
 
-import org.matrix.android.sdk.api.pushrules.RuleKind
+import org.matrix.android.sdk.api.session.pushrules.RuleKind
 import org.matrix.android.sdk.internal.network.GlobalErrorReceiver
 import org.matrix.android.sdk.internal.network.executeRequest
 import org.matrix.android.sdk.internal.task.Task
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/pushers/SavePushRulesTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/pushers/SavePushRulesTask.kt
index 6a4b891ecf..ff685e9281 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/pushers/SavePushRulesTask.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/pushers/SavePushRulesTask.kt
@@ -16,9 +16,8 @@
 package org.matrix.android.sdk.internal.session.pushers
 
 import com.zhuinden.monarchy.Monarchy
-import org.matrix.android.sdk.api.pushrules.RuleScope
-import org.matrix.android.sdk.api.pushrules.RuleSetKey
-import org.matrix.android.sdk.api.pushrules.rest.GetPushRulesResponse
+import org.matrix.android.sdk.api.session.pushrules.RuleScope
+import org.matrix.android.sdk.api.session.pushrules.RuleSetKey
 import org.matrix.android.sdk.internal.database.mapper.PushRulesMapper
 import org.matrix.android.sdk.internal.database.model.PushRulesEntity
 import org.matrix.android.sdk.internal.database.model.deleteOnCascade
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/pushers/UpdatePushRuleActionsTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/pushers/UpdatePushRuleActionsTask.kt
index 33589dc55b..454b9cdd80 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/pushers/UpdatePushRuleActionsTask.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/pushers/UpdatePushRuleActionsTask.kt
@@ -15,9 +15,9 @@
  */
 package org.matrix.android.sdk.internal.session.pushers
 
-import org.matrix.android.sdk.api.pushrules.Action
-import org.matrix.android.sdk.api.pushrules.RuleKind
-import org.matrix.android.sdk.api.pushrules.toJson
+import org.matrix.android.sdk.api.session.pushrules.Action
+import org.matrix.android.sdk.api.session.pushrules.RuleKind
+import org.matrix.android.sdk.api.session.pushrules.toJson
 import org.matrix.android.sdk.internal.network.GlobalErrorReceiver
 import org.matrix.android.sdk.internal.network.executeRequest
 import org.matrix.android.sdk.internal.task.Task
@@ -38,18 +38,18 @@ internal class DefaultUpdatePushRuleActionsTask @Inject constructor(
 ) : UpdatePushRuleActionsTask {
 
     override suspend fun execute(params: UpdatePushRuleActionsTask.Params) {
+        executeRequest(globalErrorReceiver) {
+            pushRulesApi.updateEnableRuleStatus(
+                    params.kind.value,
+                    params.ruleId,
+                    EnabledBody(params.enable)
+            )
+        }
+        if (params.actions != null) {
+            val body = mapOf("actions" to params.actions.toJson())
             executeRequest(globalErrorReceiver) {
-                pushRulesApi.updateEnableRuleStatus(
-                        params.kind.value,
-                        params.ruleId,
-                        EnabledBody(params.enable)
-                )
-            }
-            if (params.actions != null) {
-                val body = mapOf("actions" to params.actions.toJson())
-                executeRequest(globalErrorReceiver) {
-                    pushRulesApi.updateRuleActions(params.kind.value, params.ruleId, body)
-                }
+                pushRulesApi.updateRuleActions(params.kind.value, params.ruleId, body)
             }
+        }
     }
 }
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/pushers/UpdatePushRuleEnableStatusTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/pushers/UpdatePushRuleEnableStatusTask.kt
index 3fe1614678..815661a1ce 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/pushers/UpdatePushRuleEnableStatusTask.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/pushers/UpdatePushRuleEnableStatusTask.kt
@@ -15,8 +15,8 @@
  */
 package org.matrix.android.sdk.internal.session.pushers
 
-import org.matrix.android.sdk.api.pushrules.RuleKind
-import org.matrix.android.sdk.api.pushrules.rest.PushRule
+import org.matrix.android.sdk.api.session.pushrules.RuleKind
+import org.matrix.android.sdk.api.session.pushrules.rest.PushRule
 import org.matrix.android.sdk.internal.network.GlobalErrorReceiver
 import org.matrix.android.sdk.internal.network.executeRequest
 import org.matrix.android.sdk.internal.task.Task
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/notification/DefaultPushRuleService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/pushrules/DefaultPushRuleService.kt
similarity index 90%
rename from matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/notification/DefaultPushRuleService.kt
rename to matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/pushrules/DefaultPushRuleService.kt
index cdc7350f8b..ace23f1fe5 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/notification/DefaultPushRuleService.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/pushrules/DefaultPushRuleService.kt
@@ -13,23 +13,23 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package org.matrix.android.sdk.internal.session.notification
+package org.matrix.android.sdk.internal.session.pushrules
 
 import androidx.lifecycle.LiveData
 import androidx.lifecycle.Transformations
 import com.zhuinden.monarchy.Monarchy
-import org.matrix.android.sdk.api.pushrules.Action
-import org.matrix.android.sdk.api.pushrules.ConditionResolver
-import org.matrix.android.sdk.api.pushrules.PushEvents
-import org.matrix.android.sdk.api.pushrules.PushRuleService
-import org.matrix.android.sdk.api.pushrules.RuleKind
-import org.matrix.android.sdk.api.pushrules.RuleScope
-import org.matrix.android.sdk.api.pushrules.RuleSetKey
-import org.matrix.android.sdk.api.pushrules.SenderNotificationPermissionCondition
-import org.matrix.android.sdk.api.pushrules.getActions
-import org.matrix.android.sdk.api.pushrules.rest.PushRule
-import org.matrix.android.sdk.api.pushrules.rest.RuleSet
 import org.matrix.android.sdk.api.session.events.model.Event
+import org.matrix.android.sdk.api.session.pushrules.Action
+import org.matrix.android.sdk.api.session.pushrules.ConditionResolver
+import org.matrix.android.sdk.api.session.pushrules.PushEvents
+import org.matrix.android.sdk.api.session.pushrules.PushRuleService
+import org.matrix.android.sdk.api.session.pushrules.RuleKind
+import org.matrix.android.sdk.api.session.pushrules.RuleScope
+import org.matrix.android.sdk.api.session.pushrules.RuleSetKey
+import org.matrix.android.sdk.api.session.pushrules.SenderNotificationPermissionCondition
+import org.matrix.android.sdk.api.session.pushrules.getActions
+import org.matrix.android.sdk.api.session.pushrules.rest.PushRule
+import org.matrix.android.sdk.api.session.pushrules.rest.RuleSet
 import org.matrix.android.sdk.internal.database.mapper.PushRulesMapper
 import org.matrix.android.sdk.internal.database.model.PushRuleEntity
 import org.matrix.android.sdk.internal.database.model.PushRulesEntity
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/notification/ProcessEventForPushTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/pushrules/ProcessEventForPushTask.kt
similarity index 93%
rename from matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/notification/ProcessEventForPushTask.kt
rename to matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/pushrules/ProcessEventForPushTask.kt
index 8ae203c2b3..91d092a2b4 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/notification/ProcessEventForPushTask.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/pushrules/ProcessEventForPushTask.kt
@@ -14,12 +14,12 @@
  * limitations under the License.
  */
 
-package org.matrix.android.sdk.internal.session.notification
+package org.matrix.android.sdk.internal.session.pushrules
 
-import org.matrix.android.sdk.api.pushrules.PushEvents
-import org.matrix.android.sdk.api.pushrules.rest.PushRule
 import org.matrix.android.sdk.api.session.events.model.EventType
 import org.matrix.android.sdk.api.session.events.model.isInvitation
+import org.matrix.android.sdk.api.session.pushrules.PushEvents
+import org.matrix.android.sdk.api.session.pushrules.rest.PushRule
 import org.matrix.android.sdk.api.session.sync.model.RoomsSyncResponse
 import org.matrix.android.sdk.internal.di.UserId
 import org.matrix.android.sdk.internal.task.Task
@@ -57,6 +57,7 @@ internal class DefaultProcessEventForPushTask @Inject constructor(
         val allEvents = (newJoinEvents + inviteEvents).filter { event ->
             when (event.type) {
                 in EventType.POLL_START,
+                in EventType.STATE_ROOM_BEACON_INFO,
                 EventType.MESSAGE,
                 EventType.REDACTION,
                 EventType.ENCRYPTED,
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/notification/PushRuleFinder.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/pushrules/PushRuleFinder.kt
similarity index 86%
rename from matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/notification/PushRuleFinder.kt
rename to matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/pushrules/PushRuleFinder.kt
index 6e302d373d..b9d06a934d 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/notification/PushRuleFinder.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/pushrules/PushRuleFinder.kt
@@ -14,11 +14,11 @@
  * limitations under the License.
  */
 
-package org.matrix.android.sdk.internal.session.notification
+package org.matrix.android.sdk.internal.session.pushrules
 
-import org.matrix.android.sdk.api.pushrules.ConditionResolver
-import org.matrix.android.sdk.api.pushrules.rest.PushRule
 import org.matrix.android.sdk.api.session.events.model.Event
+import org.matrix.android.sdk.api.session.pushrules.ConditionResolver
+import org.matrix.android.sdk.api.session.pushrules.rest.PushRule
 import javax.inject.Inject
 
 internal class PushRuleFinder @Inject constructor(
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/DefaultRoom.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/DefaultRoom.kt
index 34e859e509..7ac6f175db 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/DefaultRoom.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/DefaultRoom.kt
@@ -18,6 +18,7 @@ package org.matrix.android.sdk.internal.session.room
 
 import androidx.lifecycle.LiveData
 import org.matrix.android.sdk.api.MatrixCoroutineDispatchers
+import org.matrix.android.sdk.api.crypto.MXCRYPTO_ALGORITHM_MEGOLM
 import org.matrix.android.sdk.api.session.crypto.CryptoService
 import org.matrix.android.sdk.api.session.events.model.EventType
 import org.matrix.android.sdk.api.session.room.Room
@@ -41,16 +42,13 @@ import org.matrix.android.sdk.api.session.room.timeline.TimelineService
 import org.matrix.android.sdk.api.session.room.typing.TypingService
 import org.matrix.android.sdk.api.session.room.uploads.UploadsService
 import org.matrix.android.sdk.api.session.room.version.RoomVersionService
-import org.matrix.android.sdk.api.session.search.SearchResult
 import org.matrix.android.sdk.api.session.space.Space
 import org.matrix.android.sdk.api.util.Optional
-import org.matrix.android.sdk.internal.crypto.MXCRYPTO_ALGORITHM_MEGOLM
+import org.matrix.android.sdk.api.util.awaitCallback
 import org.matrix.android.sdk.internal.session.permalinks.ViaParameterFinder
 import org.matrix.android.sdk.internal.session.room.state.SendStateTask
 import org.matrix.android.sdk.internal.session.room.summary.RoomSummaryDataSource
-import org.matrix.android.sdk.internal.session.search.SearchTask
 import org.matrix.android.sdk.internal.session.space.DefaultSpace
-import org.matrix.android.sdk.internal.util.awaitCallback
 import java.security.InvalidParameterException
 
 internal class DefaultRoom(override val roomId: String,
@@ -76,7 +74,6 @@ internal class DefaultRoom(override val roomId: String,
                            private val roomVersionService: RoomVersionService,
                            private val sendStateTask: SendStateTask,
                            private val viaParameterFinder: ViaParameterFinder,
-                           private val searchTask: SearchTask,
                            override val coroutineDispatchers: MatrixCoroutineDispatchers
 ) :
         Room,
@@ -140,34 +137,14 @@ internal class DefaultRoom(override val roomId: String,
                         eventType = EventType.STATE_ROOM_ENCRYPTION,
                         body = mapOf(
                                 "algorithm" to algorithm
-                        ))
+                        )
+                )
 
                 sendStateTask.execute(params)
             }
         }
     }
 
-    override suspend fun search(searchTerm: String,
-                                nextBatch: String?,
-                                orderByRecent: Boolean,
-                                limit: Int,
-                                beforeLimit: Int,
-                                afterLimit: Int,
-                                includeProfile: Boolean): SearchResult {
-        return searchTask.execute(
-                SearchTask.Params(
-                        searchTerm = searchTerm,
-                        roomId = roomId,
-                        nextBatch = nextBatch,
-                        orderByRecent = orderByRecent,
-                        limit = limit,
-                        beforeLimit = beforeLimit,
-                        afterLimit = afterLimit,
-                        includeProfile = includeProfile
-                )
-        )
-    }
-
     override fun asSpace(): Space? {
         if (roomSummary()?.roomType != RoomType.SPACE) return null
         return DefaultSpace(this, roomSummaryDataSource, viaParameterFinder)
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 0d78489fbd..8424ee8a36 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,29 +20,30 @@ import androidx.lifecycle.LiveData
 import androidx.lifecycle.Transformations
 import androidx.paging.PagedList
 import com.zhuinden.monarchy.Monarchy
-import kotlinx.coroutines.flow.Flow
+import org.matrix.android.sdk.api.query.QueryStringValue
 import org.matrix.android.sdk.api.session.events.model.Event
+import org.matrix.android.sdk.api.session.identity.model.SignInvitationResult
 import org.matrix.android.sdk.api.session.room.Room
 import org.matrix.android.sdk.api.session.room.RoomService
 import org.matrix.android.sdk.api.session.room.RoomSortOrder
 import org.matrix.android.sdk.api.session.room.RoomSummaryQueryParams
 import org.matrix.android.sdk.api.session.room.UpdatableLivePageResult
+import org.matrix.android.sdk.api.session.room.alias.RoomAliasDescription
 import org.matrix.android.sdk.api.session.room.members.ChangeMembershipState
 import org.matrix.android.sdk.api.session.room.model.Membership
 import org.matrix.android.sdk.api.session.room.model.RoomMemberSummary
 import org.matrix.android.sdk.api.session.room.model.RoomSummary
 import org.matrix.android.sdk.api.session.room.model.create.CreateRoomParams
 import org.matrix.android.sdk.api.session.room.peeking.PeekResult
+import org.matrix.android.sdk.api.session.room.roomSummaryQueryParams
 import org.matrix.android.sdk.api.session.room.summary.RoomAggregateNotificationCount
 import org.matrix.android.sdk.api.util.Optional
 import org.matrix.android.sdk.api.util.toOptional
 import org.matrix.android.sdk.internal.database.mapper.asDomain
 import org.matrix.android.sdk.internal.database.model.RoomMemberSummaryEntityFields
 import org.matrix.android.sdk.internal.di.SessionDatabase
-import org.matrix.android.sdk.internal.session.identity.model.SignInvitationResult
 import org.matrix.android.sdk.internal.session.room.alias.DeleteRoomAliasTask
 import org.matrix.android.sdk.internal.session.room.alias.GetRoomIdByAliasTask
-import org.matrix.android.sdk.internal.session.room.alias.RoomAliasDescription
 import org.matrix.android.sdk.internal.session.room.create.CreateRoomTask
 import org.matrix.android.sdk.internal.session.room.membership.RoomChangeMembershipStateDataSource
 import org.matrix.android.sdk.internal.session.room.membership.RoomMemberHelper
@@ -52,6 +53,7 @@ import org.matrix.android.sdk.internal.session.room.peeking.PeekRoomTask
 import org.matrix.android.sdk.internal.session.room.peeking.ResolveRoomStateTask
 import org.matrix.android.sdk.internal.session.room.read.MarkAllRoomsReadTask
 import org.matrix.android.sdk.internal.session.room.summary.RoomSummaryDataSource
+import org.matrix.android.sdk.internal.session.room.summary.RoomSummaryUpdater
 import org.matrix.android.sdk.internal.session.user.accountdata.UpdateBreadcrumbsTask
 import org.matrix.android.sdk.internal.util.fetchCopied
 import javax.inject.Inject
@@ -70,6 +72,7 @@ internal class DefaultRoomService @Inject constructor(
         private val roomSummaryDataSource: RoomSummaryDataSource,
         private val roomChangeMembershipStateDataSource: RoomChangeMembershipStateDataSource,
         private val leaveRoomTask: LeaveRoomTask,
+        private val roomSummaryUpdater: RoomSummaryUpdater
 ) : RoomService {
 
     override suspend fun createRoom(createRoomParams: CreateRoomParams): String {
@@ -93,6 +96,23 @@ internal class DefaultRoomService @Inject constructor(
         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)
+        })
+
+        if (roomSummaries.isNotEmpty()) {
+            monarchy.runTransactionSync { realm ->
+                roomSummaries.forEach {
+                    roomSummaryUpdater.refreshLatestPreviewContent(realm, it.roomId)
+                }
+            }
+        }
+    }
+
     override fun getRoomSummariesLive(queryParams: RoomSummaryQueryParams,
                                       sortOrder: RoomSortOrder): LiveData> {
         return roomSummaryDataSource.getRoomSummariesLive(queryParams, sortOrder)
@@ -110,8 +130,8 @@ internal class DefaultRoomService @Inject constructor(
         return roomSummaryDataSource.getUpdatablePagedRoomSummariesLive(queryParams, pagedListConfig, sortOrder)
     }
 
-    override fun getRoomCountFlow(queryParams: RoomSummaryQueryParams): Flow {
-        return roomSummaryDataSource.getCountFlow(queryParams)
+    override fun getRoomCountLive(queryParams: RoomSummaryQueryParams): LiveData {
+        return roomSummaryDataSource.getCountLive(queryParams)
     }
 
     override fun getNotificationCountForRooms(queryParams: RoomSummaryQueryParams): RoomAggregateNotificationCount {
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 4a43cfc22a..ac944ea8a7 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
@@ -16,14 +16,15 @@
 package org.matrix.android.sdk.internal.session.room
 
 import io.realm.Realm
-import org.matrix.android.sdk.api.crypto.VerificationState
 import org.matrix.android.sdk.api.extensions.orFalse
 import org.matrix.android.sdk.api.query.QueryStringValue
+import org.matrix.android.sdk.api.session.crypto.verification.VerificationState
 import org.matrix.android.sdk.api.session.events.model.AggregatedAnnotation
 import org.matrix.android.sdk.api.session.events.model.Event
 import org.matrix.android.sdk.api.session.events.model.EventType
 import org.matrix.android.sdk.api.session.events.model.LocalEcho
 import org.matrix.android.sdk.api.session.events.model.RelationType
+import org.matrix.android.sdk.api.session.events.model.content.EncryptedEventContent
 import org.matrix.android.sdk.api.session.events.model.getRelationContent
 import org.matrix.android.sdk.api.session.events.model.toContent
 import org.matrix.android.sdk.api.session.events.model.toModel
@@ -34,6 +35,7 @@ import org.matrix.android.sdk.api.session.room.model.VoteInfo
 import org.matrix.android.sdk.api.session.room.model.VoteSummary
 import org.matrix.android.sdk.api.session.room.model.message.MessageContent
 import org.matrix.android.sdk.api.session.room.model.message.MessageEndPollContent
+import org.matrix.android.sdk.api.session.room.model.message.MessageLiveLocationContent
 import org.matrix.android.sdk.api.session.room.model.message.MessagePollContent
 import org.matrix.android.sdk.api.session.room.model.message.MessagePollResponseContent
 import org.matrix.android.sdk.api.session.room.model.message.MessageRelationContent
@@ -42,7 +44,6 @@ import org.matrix.android.sdk.api.session.room.powerlevels.PowerLevelsHelper
 import org.matrix.android.sdk.api.session.room.timeline.TimelineEvent
 import org.matrix.android.sdk.api.session.room.timeline.getLastMessageContent
 import org.matrix.android.sdk.internal.SessionManager
-import org.matrix.android.sdk.internal.crypto.model.event.EncryptedEventContent
 import org.matrix.android.sdk.internal.crypto.verification.toState
 import org.matrix.android.sdk.internal.database.helper.findRootThreadEvent
 import org.matrix.android.sdk.internal.database.mapper.ContentMapper
@@ -64,6 +65,7 @@ import org.matrix.android.sdk.internal.database.query.where
 import org.matrix.android.sdk.internal.di.SessionId
 import org.matrix.android.sdk.internal.di.UserId
 import org.matrix.android.sdk.internal.session.EventInsertLiveProcessor
+import org.matrix.android.sdk.internal.session.room.aggregation.livelocation.LiveLocationAggregationProcessor
 import org.matrix.android.sdk.internal.session.room.state.StateEventDataSource
 import timber.log.Timber
 import javax.inject.Inject
@@ -72,7 +74,8 @@ internal class EventRelationsAggregationProcessor @Inject constructor(
         @UserId private val userId: String,
         private val stateEventDataSource: StateEventDataSource,
         @SessionId private val sessionId: String,
-        private val sessionManager: SessionManager
+        private val sessionManager: SessionManager,
+        private val liveLocationAggregationProcessor: LiveLocationAggregationProcessor
 ) : EventInsertLiveProcessor {
 
     private val allowedTypes = listOf(
@@ -88,7 +91,7 @@ internal class EventRelationsAggregationProcessor @Inject constructor(
             // EventType.KEY_VERIFICATION_READY,
             EventType.KEY_VERIFICATION_KEY,
             EventType.ENCRYPTED
-    ) + EventType.POLL_START + EventType.POLL_RESPONSE + EventType.POLL_END
+    ) + EventType.POLL_START + EventType.POLL_RESPONSE + EventType.POLL_END + EventType.BEACON_LOCATION_DATA
 
     override fun shouldProcess(eventId: String, eventType: String, insertType: EventInsertType): Boolean {
         return allowedTypes.contains(eventType)
@@ -103,12 +106,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 +137,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 +146,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 ||
@@ -169,22 +172,27 @@ 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 {
                                     handleResponse(realm, event, it, roomId, isLocalEcho, event.getRelationContent()?.eventId)
                                 }
                             }
-                            in EventType.POLL_END          -> {
+                            in EventType.POLL_END             -> {
                                 event.content.toModel(catchError = true)?.let {
                                     handleEndPoll(realm, event, it, roomId, isLocalEcho)
                                 }
                             }
+                            in EventType.BEACON_LOCATION_DATA -> {
+                                event.content.toModel(catchError = true)?.let {
+                                    liveLocationAggregationProcessor.handleLiveLocation(realm, event, it, roomId, isLocalEcho)
+                                }
+                            }
                         }
                     } else if (encryptedEventContent?.relatesTo?.type == RelationType.ANNOTATION) {
                         // Reaction
@@ -205,7 +213,7 @@ internal class EventRelationsAggregationProcessor @Inject constructor(
 //                                 }
 //                    }
                 }
-                EventType.REDACTION            -> {
+                EventType.REDACTION               -> {
                     val eventToPrune = event.redacts?.let { EventEntity.where(realm, eventId = it).findFirst() }
                             ?: return
                     when (eventToPrune.type) {
@@ -225,7 +233,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}")
@@ -233,17 +241,22 @@ internal class EventRelationsAggregationProcessor @Inject constructor(
                         handleReplace(realm, event, content, roomId, isLocalEcho)
                     }
                 }
-                in EventType.POLL_RESPONSE     -> {
+                in EventType.POLL_RESPONSE        -> {
                     event.content.toModel(catchError = true)?.let {
                         handleResponse(realm, event, it, roomId, isLocalEcho)
                     }
                 }
-                in EventType.POLL_END          -> {
+                in EventType.POLL_END             -> {
                     event.content.toModel(catchError = true)?.let {
                         handleEndPoll(realm, event, it, roomId, isLocalEcho)
                     }
                 }
-                else                           -> Timber.v("UnHandled event ${event.eventId}")
+                in EventType.BEACON_LOCATION_DATA -> {
+                    event.content.toModel(catchError = true)?.let {
+                        liveLocationAggregationProcessor.handleLiveLocation(realm, event, it, roomId, isLocalEcho)
+                    }
+                }
+                else                              -> Timber.v("UnHandled event ${event.eventId}")
             }
         } catch (t: Throwable) {
             Timber.e(t, "## Should not happen ")
@@ -303,14 +316,16 @@ internal class EventRelationsAggregationProcessor @Inject constructor(
             ContentMapper
                     .map(eventAnnotationsSummaryEntity.pollResponseSummary?.aggregatedContent)
                     ?.toModel()
-                    ?.apply {
-                        totalVotes = 0
-                        winnerVoteCount = 0
-                        votes = emptyList()
-                        votesSummary = emptyMap()
-                    }
-                    ?.apply {
-                        eventAnnotationsSummaryEntity.pollResponseSummary?.aggregatedContent = ContentMapper.map(toContent())
+                    ?.let { existingPollSummaryContent ->
+                        eventAnnotationsSummaryEntity.pollResponseSummary?.aggregatedContent = ContentMapper.map(
+                                PollSummaryContent(
+                                        myVote = existingPollSummaryContent.myVote,
+                                        votes = emptyList(),
+                                        votesSummary = emptyMap(),
+                                        totalVotes = 0,
+                                        winnerVoteCount = 0,
+                                )
+                                        .toContent())
                     }
 
             val txId = event.unsignedData?.transactionId
@@ -397,15 +412,15 @@ internal class EventRelationsAggregationProcessor @Inject constructor(
                     existing.pollResponseSummary = it
                 }
 
-        val closedTime = existingPollSummary?.closedTime
+        val closedTime = existingPollSummary.closedTime
         if (closedTime != null && eventTimestamp > closedTime) {
             Timber.v("## POLL is closed ignore event poll:$targetEventId, event :${event.eventId}")
             return
         }
 
-        val sumModel = ContentMapper.map(existingPollSummary?.aggregatedContent).toModel() ?: PollSummaryContent()
+        val currentModel = ContentMapper.map(existingPollSummary.aggregatedContent).toModel()
 
-        if (existingPollSummary!!.sourceEvents.contains(eventId)) {
+        if (existingPollSummary.sourceEvents.contains(eventId)) {
             // ignore this event, we already know it (??)
             Timber.v("## POLL  ignoring event for summary, it's known eventId:$eventId")
             return
@@ -430,7 +445,9 @@ internal class EventRelationsAggregationProcessor @Inject constructor(
             return
         }
 
-        val votes = sumModel.votes?.toMutableList() ?: ArrayList()
+        val votes = currentModel?.votes.orEmpty().toMutableList()
+
+        var myVote: String? = null
         val existingVoteIndex = votes.indexOfFirst { it.userId == senderId }
         if (existingVoteIndex != -1) {
             // Is the vote newer?
@@ -439,7 +456,7 @@ internal class EventRelationsAggregationProcessor @Inject constructor(
                 // Take the new one
                 votes[existingVoteIndex] = VoteInfo(senderId, option, eventTimestamp)
                 if (userId == senderId) {
-                    sumModel.myVote = option
+                    myVote = option
                 }
                 Timber.v("## POLL adding vote $option for user $senderId in poll :$targetEventId ")
             } else {
@@ -448,16 +465,14 @@ internal class EventRelationsAggregationProcessor @Inject constructor(
         } else {
             votes.add(VoteInfo(senderId, option, eventTimestamp))
             if (userId == senderId) {
-                sumModel.myVote = option
+                myVote = option
             }
             Timber.v("## POLL adding vote $option for user $senderId in poll :$targetEventId ")
         }
-        sumModel.votes = votes
 
         // Precompute the percentage of votes for all options
         val totalVotes = votes.size
-        sumModel.totalVotes = totalVotes
-        sumModel.votesSummary = votes
+        val newVotesSummary = votes
                 .groupBy({ it.option }, { it.userId })
                 .mapValues {
                     VoteSummary(
@@ -465,7 +480,7 @@ internal class EventRelationsAggregationProcessor @Inject constructor(
                             percentage = if (totalVotes == 0 && it.value.isEmpty()) 0.0 else it.value.size.toDouble() / totalVotes
                     )
                 }
-        sumModel.winnerVoteCount = sumModel.votesSummary?.maxOf { it.value.total } ?: 0
+        val newWinnerVoteCount = newVotesSummary.maxOf { it.value.total }
 
         if (isLocalEcho) {
             existingPollSummary.sourceLocalEchoEvents.add(eventId)
@@ -473,7 +488,15 @@ internal class EventRelationsAggregationProcessor @Inject constructor(
             existingPollSummary.sourceEvents.add(eventId)
         }
 
-        existingPollSummary.aggregatedContent = ContentMapper.map(sumModel.toContent())
+        val newSumModel = PollSummaryContent(
+                myVote = myVote,
+                votes = votes,
+                votesSummary = newVotesSummary,
+                totalVotes = totalVotes,
+                winnerVoteCount = newWinnerVoteCount
+        )
+
+        existingPollSummary.aggregatedContent = ContentMapper.map(newSumModel.toContent())
     }
 
     private fun handleEndPoll(realm: Realm,
@@ -482,51 +505,44 @@ internal class EventRelationsAggregationProcessor @Inject constructor(
                               roomId: String,
                               isLocalEcho: Boolean) {
         val pollEventId = content.relatesTo?.eventId ?: return
-
         val pollOwnerId = getPollEvent(roomId, pollEventId)?.root?.senderId
         val isPollOwner = pollOwnerId == event.senderId
-
         val powerLevelsHelper = stateEventDataSource.getStateEvent(roomId, EventType.STATE_ROOM_POWER_LEVELS, QueryStringValue.NoCondition)
                 ?.content?.toModel()
                 ?.let { PowerLevelsHelper(it) }
+
         if (!isPollOwner && !powerLevelsHelper?.isUserAbleToRedact(event.senderId ?: "").orFalse()) {
             Timber.v("## Received poll.end event $pollEventId but user ${event.senderId} doesn't have enough power level in room $roomId")
             return
         }
 
-        var existing = EventAnnotationsSummaryEntity.where(realm, roomId, pollEventId).findFirst()
-        if (existing == null) {
+        var existingPoll = EventAnnotationsSummaryEntity.where(realm, roomId, pollEventId).findFirst()
+        if (existingPoll == null) {
             Timber.v("## POLL creating new relation summary for $pollEventId")
-            existing = EventAnnotationsSummaryEntity.create(realm, roomId, pollEventId)
+            existingPoll = EventAnnotationsSummaryEntity.create(realm, roomId, pollEventId)
         }
 
         // we have it
-        val existingPollSummary = existing.pollResponseSummary
+        val existingPollSummary = existingPoll.pollResponseSummary
                 ?: realm.createObject(PollResponseAggregatedSummaryEntity::class.java).also {
-                    existing.pollResponseSummary = it
+                    existingPoll.pollResponseSummary = it
                 }
 
-        if (existingPollSummary.closedTime != null) {
-            Timber.v("## Received poll.end event for already ended poll $pollEventId")
-            return
-        }
-
         val txId = event.unsignedData?.transactionId
+        existingPollSummary.closedTime = event.originServerTs
+
         // is it a remote echo?
         if (!isLocalEcho && existingPollSummary.sourceLocalEchoEvents.contains(txId)) {
             // ok it has already been managed
             Timber.v("## POLL  Receiving remote echo of response eventId:$pollEventId")
             existingPollSummary.sourceLocalEchoEvents.remove(txId)
             existingPollSummary.sourceEvents.add(event.eventId)
-            return
         }
-
-        existingPollSummary.closedTime = event.originServerTs
     }
 
     private fun getPollEvent(roomId: String, eventId: String): TimelineEvent? {
         val session = sessionManager.getSessionComponent(sessionId)?.session()
-        return session?.getRoom(roomId)?.getTimelineEvent(eventId) ?: return null.also {
+        return session?.roomService()?.getRoom(roomId)?.getTimelineEvent(eventId) ?: return null.also {
             Timber.v("## POLL target poll event $eventId not found in room $roomId")
         }
     }
@@ -591,11 +607,11 @@ internal class EventRelationsAggregationProcessor @Inject constructor(
                 sum.key = reaction
                 sum.firstTimestamp = event.originServerTs ?: 0
                 if (isLocalEcho) {
-                    Timber.v("Adding local echo reaction $reaction")
+                    Timber.v("Adding local echo reaction")
                     sum.sourceLocalEcho.add(txId)
                     sum.count = 1
                 } else {
-                    Timber.v("Adding synced reaction $reaction")
+                    Timber.v("Adding synced reaction")
                     sum.count = 1
                     sum.sourceEvents.add(reactionEventId)
                 }
@@ -607,16 +623,16 @@ internal class EventRelationsAggregationProcessor @Inject constructor(
                     // check if it's not the sync of a local echo
                     if (!isLocalEcho && sum.sourceLocalEcho.contains(txId)) {
                         // ok it has already been counted, just sync the list, do not touch count
-                        Timber.v("Ignoring synced of local echo for reaction $reaction")
+                        Timber.v("Ignoring synced of local echo for reaction")
                         sum.sourceLocalEcho.remove(txId)
                         sum.sourceEvents.add(reactionEventId)
                     } else {
                         sum.count += 1
                         if (isLocalEcho) {
-                            Timber.v("Adding local echo reaction $reaction")
+                            Timber.v("Adding local echo reaction")
                             sum.sourceLocalEcho.add(txId)
                         } else {
-                            Timber.v("Adding synced reaction $reaction")
+                            Timber.v("Adding synced reaction")
                             sum.sourceEvents.add(reactionEventId)
                         }
 
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/RoomFactory.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/RoomFactory.kt
index 72a3f9ab22..50b20ec50b 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
@@ -41,7 +41,6 @@ import org.matrix.android.sdk.internal.session.room.timeline.DefaultTimelineServ
 import org.matrix.android.sdk.internal.session.room.typing.DefaultTypingService
 import org.matrix.android.sdk.internal.session.room.uploads.DefaultUploadsService
 import org.matrix.android.sdk.internal.session.room.version.DefaultRoomVersionService
-import org.matrix.android.sdk.internal.session.search.SearchTask
 import javax.inject.Inject
 
 internal interface RoomFactory {
@@ -71,7 +70,6 @@ internal class DefaultRoomFactory @Inject constructor(private val cryptoService:
                                                       private val roomAccountDataServiceFactory: DefaultRoomAccountDataService.Factory,
                                                       private val sendStateTask: SendStateTask,
                                                       private val viaParameterFinder: ViaParameterFinder,
-                                                      private val searchTask: SearchTask,
                                                       private val coroutineDispatchers: MatrixCoroutineDispatchers) :
         RoomFactory {
 
@@ -99,7 +97,6 @@ internal class DefaultRoomFactory @Inject constructor(private val cryptoService:
                 roomAccountDataService = roomAccountDataServiceFactory.create(roomId),
                 roomVersionService = roomVersionServiceFactory.create(roomId),
                 sendStateTask = sendStateTask,
-                searchTask = searchTask,
                 viaParameterFinder = viaParameterFinder,
                 coroutineDispatchers = coroutineDispatchers
         )
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/aggregation/livelocation/DefaultLiveLocationAggregationProcessor.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/aggregation/livelocation/DefaultLiveLocationAggregationProcessor.kt
new file mode 100644
index 0000000000..8de0965b40
--- /dev/null
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/aggregation/livelocation/DefaultLiveLocationAggregationProcessor.kt
@@ -0,0 +1,85 @@
+/*
+ * Copyright 2022 The Matrix.org Foundation C.I.C.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.matrix.android.sdk.internal.session.room.aggregation.livelocation
+
+import io.realm.Realm
+import org.matrix.android.sdk.api.extensions.orFalse
+import org.matrix.android.sdk.api.session.events.model.Event
+import org.matrix.android.sdk.api.session.events.model.EventType
+import org.matrix.android.sdk.api.session.events.model.toContent
+import org.matrix.android.sdk.api.session.events.model.toModel
+import org.matrix.android.sdk.api.session.room.model.livelocation.LiveLocationBeaconContent
+import org.matrix.android.sdk.api.session.room.model.message.MessageLiveLocationContent
+import org.matrix.android.sdk.internal.database.mapper.ContentMapper
+import org.matrix.android.sdk.internal.database.model.CurrentStateEventEntity
+import org.matrix.android.sdk.internal.database.query.getOrNull
+import timber.log.Timber
+import javax.inject.Inject
+
+internal class DefaultLiveLocationAggregationProcessor @Inject constructor() : LiveLocationAggregationProcessor {
+
+    override fun handleLiveLocation(realm: Realm, event: Event, content: MessageLiveLocationContent, roomId: String, isLocalEcho: Boolean) {
+        val locationSenderId = event.senderId ?: return
+
+        // We shouldn't process local echos
+        if (isLocalEcho) {
+            return
+        }
+
+        // A beacon info state event has to be sent before sending location
+        // TODO handle missing check of m_relatesTo field
+        var beaconInfoEntity: CurrentStateEventEntity? = null
+        val eventTypesIterator = EventType.STATE_ROOM_BEACON_INFO.iterator()
+        while (beaconInfoEntity == null && eventTypesIterator.hasNext()) {
+            beaconInfoEntity = CurrentStateEventEntity.getOrNull(realm, roomId, locationSenderId, eventTypesIterator.next())
+        }
+
+        if (beaconInfoEntity == null) {
+            Timber.v("## LIVE LOCATION. There is not any beacon info which should be emitted before sending location updates")
+            return
+        }
+        val beaconInfoContent = ContentMapper.map(beaconInfoEntity.root?.content)?.toModel(catchError = true)
+        if (beaconInfoContent == null) {
+            Timber.v("## LIVE LOCATION. Beacon info content is invalid")
+            return
+        }
+
+        // Check if live location is ended
+        if (!beaconInfoContent.isLive.orFalse()) {
+            Timber.v("## LIVE LOCATION. Beacon info is not live anymore")
+            return
+        }
+
+        // Check if beacon info is outdated
+        if (isBeaconInfoOutdated(beaconInfoContent, content)) {
+            Timber.v("## LIVE LOCATION. Beacon info has timeout")
+            beaconInfoContent.hasTimedOut = true
+        } else {
+            beaconInfoContent.lastLocationContent = content
+        }
+
+        beaconInfoEntity.root?.content = ContentMapper.map(beaconInfoContent.toContent())
+    }
+
+    private fun isBeaconInfoOutdated(beaconInfoContent: LiveLocationBeaconContent,
+                                     liveLocationContent: MessageLiveLocationContent): Boolean {
+        val beaconInfoStartTime = beaconInfoContent.getBestTimestampAsMilliseconds() ?: 0
+        val liveLocationEventTime = liveLocationContent.getBestTimestampAsMilliseconds() ?: 0
+        val timeout = beaconInfoContent.timeout ?: 0
+        return liveLocationEventTime - beaconInfoStartTime > timeout
+    }
+}
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
new file mode 100644
index 0000000000..7b5f23e243
--- /dev/null
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/aggregation/livelocation/LiveLocationAggregationProcessor.kt
@@ -0,0 +1,29 @@
+/*
+ * 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.session.room.aggregation.livelocation
+
+import io.realm.Realm
+import org.matrix.android.sdk.api.session.events.model.Event
+import org.matrix.android.sdk.api.session.room.model.message.MessageLiveLocationContent
+
+internal interface LiveLocationAggregationProcessor {
+    fun handleLiveLocation(realm: Realm,
+                           event: Event,
+                           content: MessageLiveLocationContent,
+                           roomId: String,
+                           isLocalEcho: Boolean)
+}
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/alias/GetRoomIdByAliasTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/alias/GetRoomIdByAliasTask.kt
index 71c8c9cd38..b25ef7ba0f 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/alias/GetRoomIdByAliasTask.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/alias/GetRoomIdByAliasTask.kt
@@ -19,6 +19,7 @@ package org.matrix.android.sdk.internal.session.room.alias
 import com.zhuinden.monarchy.Monarchy
 import io.realm.Realm
 import org.matrix.android.sdk.api.extensions.tryOrNull
+import org.matrix.android.sdk.api.session.room.alias.RoomAliasDescription
 import org.matrix.android.sdk.api.util.Optional
 import org.matrix.android.sdk.internal.database.model.RoomSummaryEntity
 import org.matrix.android.sdk.internal.database.query.findByAlias
@@ -51,7 +52,7 @@ internal class DefaultGetRoomIdByAliasTask @Inject constructor(
         } else if (!params.searchOnServer) {
             Optional.from(null)
         } else {
-            val description  = tryOrNull("## Failed to get roomId from alias") {
+            val description = tryOrNull("## Failed to get roomId from alias") {
                 executeRequest(globalErrorReceiver) {
                     directoryAPI.getRoomIdByAlias(params.roomAlias)
                 }
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/create/CreateRoomBodyBuilder.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/create/CreateRoomBodyBuilder.kt
index c9914449c3..3b2e9d3d22 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/create/CreateRoomBodyBuilder.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/create/CreateRoomBodyBuilder.kt
@@ -16,6 +16,7 @@
 
 package org.matrix.android.sdk.internal.session.room.create
 
+import org.matrix.android.sdk.api.crypto.MXCRYPTO_ALGORITHM_MEGOLM
 import org.matrix.android.sdk.api.extensions.tryOrNull
 import org.matrix.android.sdk.api.session.events.model.Event
 import org.matrix.android.sdk.api.session.events.model.EventType
@@ -24,7 +25,6 @@ import org.matrix.android.sdk.api.session.identity.toMedium
 import org.matrix.android.sdk.api.session.room.model.create.CreateRoomParams
 import org.matrix.android.sdk.api.util.MimeTypes
 import org.matrix.android.sdk.internal.crypto.DeviceListManager
-import org.matrix.android.sdk.internal.crypto.MXCRYPTO_ALGORITHM_MEGOLM
 import org.matrix.android.sdk.internal.di.AuthenticatedIdentity
 import org.matrix.android.sdk.internal.di.UserId
 import org.matrix.android.sdk.internal.network.token.AccessTokenProvider
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 3d0f51b831..70ba9287a2 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
@@ -123,7 +123,7 @@ internal class DefaultLoadRoomMembersTask @Inject constructor(
                     eventId = roomMemberEvent.eventId
                     root = eventEntity
                 }
-                roomMemberEventHandler.handle(realm, roomId, roomMemberEvent)
+                roomMemberEventHandler.handle(realm, roomId, roomMemberEvent, false)
             }
             roomEntity.membersLoadStatus = RoomMembersLoadStatusType.LOADED
             roomSummaryUpdater.update(realm, roomId, updateMembers = true)
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 25c124bd6b..85300fa351 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
@@ -17,6 +17,7 @@
 package org.matrix.android.sdk.internal.session.room.membership
 
 import io.realm.Realm
+import org.matrix.android.sdk.api.session.events.model.Content
 import org.matrix.android.sdk.api.session.events.model.Event
 import org.matrix.android.sdk.api.session.events.model.EventType
 import org.matrix.android.sdk.api.session.room.model.RoomMemberContent
@@ -33,23 +34,49 @@ internal class RoomMemberEventHandler @Inject constructor(
         @UserId private val myUserId: String
 ) {
 
-    fun handle(realm: Realm, roomId: String, event: Event, 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
         }
-        val userId = event.stateKey ?: return false
-        val roomMember = event.getFixedRoomMemberContent()
-        return handle(realm, roomId, userId, roomMember, aggregator)
+        val eventUserId = event.stateKey ?: return false
+        val roomMember = event.getFixedRoomMemberContent() ?: return false
+
+        return if (isInitialSync) {
+            handleInitialSync(realm, roomId, myUserId, eventUserId, roomMember, aggregator)
+        } else {
+            handleIncrementalSync(
+                    realm,
+                    roomId,
+                    eventUserId,
+                    roomMember,
+                    event.resolvedPrevContent(),
+                    aggregator
+            )
+        }
     }
 
-    fun handle(realm: Realm,
-               roomId: String,
-               userId: String,
-               roomMember: RoomMemberContent?,
-               aggregator: SyncResponsePostTreatmentAggregator? = null): Boolean {
-        if (roomMember == null) {
-            return false
+    private fun handleInitialSync(realm: Realm,
+                                  roomId: String,
+                                  currentUserId: String,
+                                  eventUserId: String,
+                                  roomMember: RoomMemberContent,
+                                  aggregator: SyncResponsePostTreatmentAggregator?): Boolean {
+        if (currentUserId != eventUserId) {
+            saveUserEntityLocallyIfNecessary(realm, eventUserId, roomMember)
         }
+        saveRoomMemberEntityLocally(realm, roomId, eventUserId, roomMember)
+        updateDirectChatsIfNecessary(roomId, roomMember, aggregator)
+        return true
+    }
+
+    private fun saveRoomMemberEntityLocally(realm: Realm,
+                                            roomId: String,
+                                            userId: String,
+                                            roomMember: RoomMemberContent) {
         val roomMemberEntity = RoomMemberEntityFactory.create(
                 roomId,
                 userId,
@@ -58,26 +85,58 @@ internal class RoomMemberEventHandler @Inject constructor(
                 // but we want to preserve presence record value and not replace it with null
                 getExistingPresenceState(realm, roomId, userId))
         realm.insertOrUpdate(roomMemberEntity)
-        if (roomMember.membership.isActive()) {
-            val userEntity = UserEntityFactory.create(userId, roomMember)
-            realm.insertOrUpdate(userEntity)
-        }
-
-        // 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
-        if (mxId != null && mxId != myUserId) {
-            aggregator?.directChatsToCheck?.put(roomId, mxId)
-        }
-        return true
     }
 
     /**
      * Get the already existing presence state for a specific user & room in order NOT to be replaced in RoomMemberSummaryEntity
      * by NULL value.
      */
-
     private fun getExistingPresenceState(realm: Realm, roomId: String, userId: String): UserPresenceEntity? {
         return RoomMemberSummaryEntity.where(realm, roomId, userId).findFirst()?.userPresenceEntity
     }
+
+    private fun saveUserEntityLocallyIfNecessary(realm: Realm,
+                                                 userId: String,
+                                                 roomMember: RoomMemberContent) {
+        if (roomMember.membership.isActive()) {
+            saveUserLocally(realm, userId, roomMember)
+        }
+    }
+
+    private fun saveUserLocally(realm: Realm, userId: String, roomMember: RoomMemberContent) {
+        val userEntity = UserEntityFactory.create(userId, roomMember)
+        realm.insertOrUpdate(userEntity)
+    }
+
+    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
+        if (mxId != null && mxId != myUserId) {
+            aggregator?.directChatsToCheck?.put(roomId, mxId)
+        }
+    }
+
+    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
+
+            if (previousDisplayName != roomMember.displayName || previousAvatar != roomMember.avatarUrl) {
+                aggregator.userIdsToFetch.add(eventUserId)
+            }
+        }
+
+        saveRoomMemberEntityLocally(realm, roomId, eventUserId, roomMember)
+        // At the end of the sync, fetch all the profiles from the aggregator
+        updateDirectChatsIfNecessary(roomId, roomMember, aggregator)
+        return true
+    }
 }
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/membership/joining/InviteBody.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/membership/joining/InviteBody.kt
index 06b75709a2..300cc210bc 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/membership/joining/InviteBody.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/membership/joining/InviteBody.kt
@@ -20,7 +20,7 @@ import com.squareup.moshi.Json
 import com.squareup.moshi.JsonClass
 
 @JsonClass(generateAdapter = true)
-data class InviteBody(
+internal data class InviteBody(
         @Json(name = "user_id") val userId: String,
         @Json(name = "reason") val reason: String?
 )
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/membership/joining/JoinRoomTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/membership/joining/JoinRoomTask.kt
index 22a46b6cfc..f883cc33ec 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/membership/joining/JoinRoomTask.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/membership/joining/JoinRoomTask.kt
@@ -19,6 +19,7 @@ package org.matrix.android.sdk.internal.session.room.membership.joining
 import io.realm.RealmConfiguration
 import kotlinx.coroutines.TimeoutCancellationException
 import org.matrix.android.sdk.api.session.events.model.toContent
+import org.matrix.android.sdk.api.session.identity.model.SignInvitationResult
 import org.matrix.android.sdk.api.session.room.failure.JoinRoomFailure
 import org.matrix.android.sdk.api.session.room.members.ChangeMembershipState
 import org.matrix.android.sdk.api.session.room.model.Membership
@@ -30,7 +31,6 @@ import org.matrix.android.sdk.internal.database.query.where
 import org.matrix.android.sdk.internal.di.SessionDatabase
 import org.matrix.android.sdk.internal.network.GlobalErrorReceiver
 import org.matrix.android.sdk.internal.network.executeRequest
-import org.matrix.android.sdk.internal.session.identity.model.SignInvitationResult
 import org.matrix.android.sdk.internal.session.room.RoomAPI
 import org.matrix.android.sdk.internal.session.room.membership.RoomChangeMembershipStateDataSource
 import org.matrix.android.sdk.internal.session.room.read.SetReadMarkersTask
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/notification/DefaultRoomPushRuleService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/notification/DefaultRoomPushRuleService.kt
index 8f1aefb731..85f53e1346 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/notification/DefaultRoomPushRuleService.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/notification/DefaultRoomPushRuleService.kt
@@ -22,7 +22,7 @@ import com.zhuinden.monarchy.Monarchy
 import dagger.assisted.Assisted
 import dagger.assisted.AssistedFactory
 import dagger.assisted.AssistedInject
-import org.matrix.android.sdk.api.pushrules.RuleScope
+import org.matrix.android.sdk.api.session.pushrules.RuleScope
 import org.matrix.android.sdk.api.session.room.notification.RoomNotificationState
 import org.matrix.android.sdk.api.session.room.notification.RoomPushRuleService
 import org.matrix.android.sdk.internal.database.model.PushRuleEntity
@@ -32,7 +32,7 @@ import org.matrix.android.sdk.internal.di.SessionDatabase
 internal class DefaultRoomPushRuleService @AssistedInject constructor(@Assisted private val roomId: String,
                                                                       private val setRoomNotificationStateTask: SetRoomNotificationStateTask,
                                                                       @SessionDatabase private val monarchy: Monarchy) :
-    RoomPushRuleService {
+        RoomPushRuleService {
 
     @AssistedFactory
     interface Factory {
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/notification/RoomPushRule.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/notification/RoomPushRule.kt
index d2c0d13b51..2c74e2a1e5 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/notification/RoomPushRule.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/notification/RoomPushRule.kt
@@ -16,8 +16,8 @@
 
 package org.matrix.android.sdk.internal.session.room.notification
 
-import org.matrix.android.sdk.api.pushrules.RuleKind
-import org.matrix.android.sdk.api.pushrules.rest.PushRule
+import org.matrix.android.sdk.api.session.pushrules.RuleKind
+import org.matrix.android.sdk.api.session.pushrules.rest.PushRule
 
 internal data class RoomPushRule(
         val kind: RuleKind,
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/notification/RoomPushRuleMapper.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/notification/RoomPushRuleMapper.kt
index 86d2e6c619..a5a5ab58ba 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/notification/RoomPushRuleMapper.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/notification/RoomPushRuleMapper.kt
@@ -16,13 +16,13 @@
 
 package org.matrix.android.sdk.internal.session.room.notification
 
-import org.matrix.android.sdk.api.pushrules.Action
-import org.matrix.android.sdk.api.pushrules.Kind
-import org.matrix.android.sdk.api.pushrules.RuleSetKey
-import org.matrix.android.sdk.api.pushrules.getActions
-import org.matrix.android.sdk.api.pushrules.rest.PushCondition
-import org.matrix.android.sdk.api.pushrules.rest.PushRule
-import org.matrix.android.sdk.api.pushrules.toJson
+import org.matrix.android.sdk.api.session.pushrules.Action
+import org.matrix.android.sdk.api.session.pushrules.Kind
+import org.matrix.android.sdk.api.session.pushrules.RuleSetKey
+import org.matrix.android.sdk.api.session.pushrules.getActions
+import org.matrix.android.sdk.api.session.pushrules.rest.PushCondition
+import org.matrix.android.sdk.api.session.pushrules.rest.PushRule
+import org.matrix.android.sdk.api.session.pushrules.toJson
 import org.matrix.android.sdk.api.session.room.notification.RoomNotificationState
 import org.matrix.android.sdk.internal.database.mapper.PushRulesMapper
 import org.matrix.android.sdk.internal.database.model.PushRuleEntity
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/notification/SetRoomNotificationStateTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/notification/SetRoomNotificationStateTask.kt
index feb8c27b09..021d7dbefb 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/notification/SetRoomNotificationStateTask.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/notification/SetRoomNotificationStateTask.kt
@@ -18,7 +18,7 @@ package org.matrix.android.sdk.internal.session.room.notification
 
 import com.zhuinden.monarchy.Monarchy
 import io.realm.Realm
-import org.matrix.android.sdk.api.pushrules.RuleScope
+import org.matrix.android.sdk.api.session.pushrules.RuleScope
 import org.matrix.android.sdk.api.session.room.notification.RoomNotificationState
 import org.matrix.android.sdk.internal.database.model.PushRuleEntity
 import org.matrix.android.sdk.internal.database.query.where
@@ -38,7 +38,7 @@ internal interface SetRoomNotificationStateTask : Task {
 //                    eventRelationsAggregationUpdater.handleReactionRedact(eventToPrune, realm, userId)
@@ -104,6 +109,39 @@ internal class RedactionEventProcessor @Inject constructor() : EventInsertLivePr
         }
     }
 
+    /**
+     * Invalidates the number of threads in the main timeline thread summary,
+     * with respect to redactions.
+     */
+    private fun handleTimelineThreadSummaryIfNeeded(
+            realm: Realm,
+            eventToPrune: EventEntity,
+            isLocalEcho: Boolean,
+    ) {
+        if (eventToPrune.isThread() && !isLocalEcho) {
+            val roomId = eventToPrune.roomId
+            val rootThreadEvent = eventToPrune.findRootThreadEvent() ?: return
+            val rootThreadEventId = eventToPrune.rootThreadEventId ?: return
+
+            val inThreadMessages = countInThreadMessages(
+                    realm = realm,
+                    roomId = roomId,
+                    rootThreadEventId = rootThreadEventId
+            )
+
+            rootThreadEvent.numberOfThreads = inThreadMessages
+            if (inThreadMessages == 0) {
+                // We should also clear the thread summary list
+                rootThreadEvent.isRootThread = false
+                rootThreadEvent.threadSummaryLatestMessage = null
+                ThreadSummaryEntity
+                        .where(realm, roomId = roomId, rootThreadEventId)
+                        .findFirst()
+                        ?.deleteFromRealm()
+            }
+        }
+    }
+
     private fun computeAllowedKeys(type: String): List {
         // Add filtered content, allowed keys in content depends on the event type
         return when (type) {
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/read/FullyReadContent.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/read/FullyReadContent.kt
index 9b4db795ec..00dfe6d29c 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/read/FullyReadContent.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/read/FullyReadContent.kt
@@ -20,6 +20,6 @@ import com.squareup.moshi.Json
 import com.squareup.moshi.JsonClass
 
 @JsonClass(generateAdapter = true)
-data class FullyReadContent(
+internal data class FullyReadContent(
         @Json(name = "event_id") val eventId: String
 )
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 d316eed691..b596f2288e 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
@@ -44,7 +44,7 @@ internal interface FetchThreadSummariesTask : Task(context, params, sessionManager, Params::class.java) {
+        SessionSafeCoroutineWorker(context, params, sessionManager, Params::class.java) {
 
     @JsonClass(generateAdapter = true)
     internal data class Params(
@@ -77,10 +76,10 @@ internal class MultipleEventSendingDispatcherWorker(context: Context, params: Wo
         params.localEchoIds.forEach { localEchoIds ->
             val roomId = localEchoIds.roomId
             val eventId = localEchoIds.eventId
-                localEchoRepository.updateSendState(eventId, roomId, SendState.SENDING)
-                Timber.v("## SendEvent: [${System.currentTimeMillis()}] Schedule send event $eventId")
-                val sendWork = createSendEventWork(params.sessionId, eventId, true)
-                timelineSendEventWorkCommon.postWork(roomId, sendWork)
+            localEchoRepository.updateSendState(eventId, roomId, SendState.SENDING)
+            Timber.v("## SendEvent: [${System.currentTimeMillis()}] Schedule send event $eventId")
+            val sendWork = createSendEventWork(params.sessionId, eventId, true)
+            timelineSendEventWorkCommon.postWork(roomId, sendWork)
         }
 
         return Result.success()
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/send/RedactEventWorker.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/send/RedactEventWorker.kt
index c03d1fa81e..83c61d2845 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/send/RedactEventWorker.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/send/RedactEventWorker.kt
@@ -34,7 +34,7 @@ import javax.inject.Inject
  * Possible next worker    : None
  */
 internal class RedactEventWorker(context: Context, params: WorkerParameters, sessionManager: SessionManager) :
-    SessionSafeCoroutineWorker(context, params, sessionManager, Params::class.java) {
+        SessionSafeCoroutineWorker(context, params, sessionManager, Params::class.java) {
 
     @JsonClass(generateAdapter = true)
     internal data class Params(
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/send/SendEventWorker.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/send/SendEventWorker.kt
index 7f24688ece..669891c822 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/send/SendEventWorker.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/send/SendEventWorker.kt
@@ -40,7 +40,7 @@ import javax.inject.Inject
  * Possible next worker    : None
  */
 internal class SendEventWorker(context: Context, params: WorkerParameters, sessionManager: SessionManager) :
-    SessionSafeCoroutineWorker(context, params, sessionManager, Params::class.java) {
+        SessionSafeCoroutineWorker(context, params, sessionManager, Params::class.java) {
 
     @JsonClass(generateAdapter = true)
     internal data class Params(
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/send/TextContent.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/send/TextContentExtension.kt
similarity index 86%
rename from matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/send/TextContent.kt
rename to matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/send/TextContentExtension.kt
index 93c0167abe..8caa99d90a 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/send/TextContent.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/send/TextContentExtension.kt
@@ -24,18 +24,9 @@ import org.matrix.android.sdk.api.session.room.model.relation.RelationDefaultCon
 import org.matrix.android.sdk.api.session.room.model.relation.ReplyToContent
 import org.matrix.android.sdk.api.util.ContentUtils.extractUsefulTextFromHtmlReply
 import org.matrix.android.sdk.api.util.ContentUtils.extractUsefulTextFromReply
+import org.matrix.android.sdk.api.util.TextContent
 
-/**
- * Contains a text and eventually a formatted text
- */
-data class TextContent(
-        val text: String,
-        val formattedText: String? = null
-) {
-    fun takeFormatted() = formattedText ?: text
-}
-
-fun TextContent.toMessageTextContent(msgType: String = MessageType.MSGTYPE_TEXT): MessageTextContent {
+internal fun TextContent.toMessageTextContent(msgType: String = MessageType.MSGTYPE_TEXT): MessageTextContent {
     return MessageTextContent(
             msgType = msgType,
             format = MessageFormat.FORMAT_MATRIX_HTML.takeIf { formattedText != null },
@@ -49,7 +40,7 @@ fun TextContent.toMessageTextContent(msgType: String = MessageType.MSGTYPE_TEXT)
  * latestThreadEventId in order for the clients without threads enabled to render it appropriately
  * If latest event not found, we pass rootThreadEventId
  */
-fun TextContent.toThreadTextContent(
+internal fun TextContent.toThreadTextContent(
         rootThreadEventId: String,
         latestThreadEventId: String,
         msgType: String = MessageType.MSGTYPE_TEXT): MessageTextContent {
@@ -68,7 +59,7 @@ fun TextContent.toThreadTextContent(
     )
 }
 
-fun TextContent.removeInReplyFallbacks(): TextContent {
+internal fun TextContent.removeInReplyFallbacks(): TextContent {
     return copy(
             text = extractUsefulTextFromReply(this.text),
             formattedText = this.formattedText?.let { extractUsefulTextFromHtmlReply(it) }
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/send/queue/QueueMemento.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/send/queue/QueueMemento.kt
index 116c8d5c6b..545fc41737 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/send/queue/QueueMemento.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/send/queue/QueueMemento.kt
@@ -74,7 +74,7 @@ internal class QueueMemento @Inject constructor(context: Context,
                     encrypt = task.encrypt,
                     order = order
             )
-            is RedactQueuedTask -> RedactEventTaskInfo(
+            is RedactQueuedTask    -> RedactEventTaskInfo(
                     redactionLocalEcho = task.redactionLocalEchoId,
                     order = order
             )
@@ -92,7 +92,7 @@ internal class QueueMemento @Inject constructor(context: Context,
                 ?.forEach { info ->
                     try {
                         when (info) {
-                            is SendEventTaskInfo -> {
+                            is SendEventTaskInfo   -> {
                                 localEchoRepository.getUpToDateEcho(info.localEchoId)?.let {
                                     if (it.sendState.isSending() && it.eventId != null && it.roomId != null) {
                                         localEchoRepository.updateSendState(it.eventId, it.roomId, SendState.UNSENT)
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/state/DefaultStateService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/state/DefaultStateService.kt
index 417417f439..0a4fd875d5 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,16 +21,19 @@ 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.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.livelocation.LiveLocationBeaconContent
 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
@@ -186,4 +189,35 @@ 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/SafePowerLevelContent.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/state/SafePowerLevelContent.kt
index 197b4f8688..1f2ec09367 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/state/SafePowerLevelContent.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/state/SafePowerLevelContent.kt
@@ -51,7 +51,7 @@ internal fun JsonDict.toSafePowerLevelsContentDict(): JsonDict {
                         usersDefault = content.usersDefault,
                         users = content.users,
                         stateDefault = content.stateDefault,
-                        notifications = content.notifications?.mapValues { content.notificationLevel(it.key)  }
+                        notifications = content.notifications?.mapValues { content.notificationLevel(it.key) }
                 )
             }
             ?.toContent()
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/state/StateEventDataSource.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/state/StateEventDataSource.kt
index 2114b9c590..42d6677409 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
@@ -32,7 +32,6 @@ import org.matrix.android.sdk.internal.database.model.CurrentStateEventEntity
 import org.matrix.android.sdk.internal.database.model.CurrentStateEventEntityFields
 import org.matrix.android.sdk.internal.di.SessionDatabase
 import org.matrix.android.sdk.internal.query.QueryStringValueProcessor
-import org.matrix.android.sdk.internal.query.process
 import javax.inject.Inject
 
 internal class StateEventDataSource @Inject constructor(
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 b7e6548b54..52879d7121 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/summary/GraphUtils.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/summary/GraphUtils.kt
@@ -18,16 +18,16 @@ package org.matrix.android.sdk.internal.session.room.summary
 
 import java.util.LinkedList
 
-data class GraphNode(
+internal data class GraphNode(
         val name: String
 )
 
-data class GraphEdge(
+internal data class GraphEdge(
         val source: GraphNode,
         val destination: GraphNode
 )
 
-class Graph {
+internal class Graph {
 
     private val adjacencyList: HashMap> = HashMap()
 
@@ -101,11 +101,11 @@ class Graph {
                         // it's a candidate
                         destination = it.destination
                     }
-                    inPath -> {
+                    inPath     -> {
                         // Cycle!!
                         backwardEdges.add(it)
                     }
-                    completed -> {
+                    completed  -> {
                         // dead end
                     }
                 }
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/summary/RoomSummaryDataSource.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/summary/RoomSummaryDataSource.kt
index ea4f102fa5..18a4f80547 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
@@ -25,12 +25,7 @@ import androidx.paging.PagedList
 import com.zhuinden.monarchy.Monarchy
 import io.realm.Realm
 import io.realm.RealmQuery
-import io.realm.kotlin.toFlow
 import io.realm.kotlin.where
-import kotlinx.coroutines.flow.Flow
-import kotlinx.coroutines.flow.distinctUntilChanged
-import kotlinx.coroutines.flow.flowOn
-import kotlinx.coroutines.flow.map
 import org.matrix.android.sdk.api.MatrixCoroutineDispatchers
 import org.matrix.android.sdk.api.query.ActiveSpaceFilter
 import org.matrix.android.sdk.api.query.RoomCategoryFilter
@@ -241,15 +236,14 @@ internal class RoomSummaryDataSource @Inject constructor(
         }
     }
 
-    fun getCountFlow(queryParams: RoomSummaryQueryParams): Flow =
-            realmSessionProvider
-                    .withRealm { realm -> roomSummariesQuery(realm, queryParams).findAllAsync() }
-                    .toFlow()
-                    // need to create the flow on a context dispatcher with a thread with attached Looper
-                    .flowOn(coroutineDispatchers.main)
-                    .map { it.size }
-                    .flowOn(coroutineDispatchers.io)
-                    .distinctUntilChanged()
+    fun getCountLive(queryParams: RoomSummaryQueryParams): LiveData {
+        val liveRooms = monarchy.findAllManagedWithChanges {
+            roomSummariesQuery(it, queryParams)
+        }
+        return Transformations.map(liveRooms) {
+            it.realmResults.where().count().toInt()
+        }
+    }
 
     fun getNotificationCountForRooms(queryParams: RoomSummaryQueryParams): RoomAggregateNotificationCount {
         var notificationCount: RoomAggregateNotificationCount? = null
@@ -314,6 +308,7 @@ internal class RoomSummaryDataSource @Inject constructor(
             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
         }
 
         // Timber.w("VAL: activeSpaceId : ${queryParams.activeSpaceId}")
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 c9712c5721..3af579d050 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
@@ -22,6 +22,7 @@ import kotlinx.coroutines.runBlocking
 import org.matrix.android.sdk.api.extensions.orFalse
 import org.matrix.android.sdk.api.extensions.tryOrNull
 import org.matrix.android.sdk.api.session.events.model.EventType
+import org.matrix.android.sdk.api.session.events.model.content.EncryptionEventContent
 import org.matrix.android.sdk.api.session.events.model.toModel
 import org.matrix.android.sdk.api.session.room.accountdata.RoomAccountDataTypes
 import org.matrix.android.sdk.api.session.room.model.Membership
@@ -40,7 +41,6 @@ import org.matrix.android.sdk.api.session.sync.model.RoomSyncSummary
 import org.matrix.android.sdk.api.session.sync.model.RoomSyncUnreadNotifications
 import org.matrix.android.sdk.internal.crypto.EventDecryptor
 import org.matrix.android.sdk.internal.crypto.crosssigning.DefaultCrossSigningService
-import org.matrix.android.sdk.internal.crypto.model.event.EncryptionEventContent
 import org.matrix.android.sdk.internal.database.mapper.ContentMapper
 import org.matrix.android.sdk.internal.database.mapper.asDomain
 import org.matrix.android.sdk.internal.database.model.CurrentStateEventEntity
@@ -64,7 +64,6 @@ import org.matrix.android.sdk.internal.session.room.accountdata.RoomAccountDataD
 import org.matrix.android.sdk.internal.session.room.membership.RoomDisplayNameResolver
 import org.matrix.android.sdk.internal.session.room.membership.RoomMemberHelper
 import org.matrix.android.sdk.internal.session.room.relationship.RoomChildRelationInfo
-import org.matrix.android.sdk.internal.util.Normalizer
 import timber.log.Timber
 import javax.inject.Inject
 import kotlin.system.measureTimeMillis
@@ -75,8 +74,16 @@ internal class RoomSummaryUpdater @Inject constructor(
         private val roomAvatarResolver: RoomAvatarResolver,
         private val eventDecryptor: EventDecryptor,
         private val crossSigningService: DefaultCrossSigningService,
-        private val roomAccountDataDataSource: RoomAccountDataDataSource,
-        private val normalizer: Normalizer) {
+        private val roomAccountDataDataSource: RoomAccountDataDataSource
+) {
+
+    fun refreshLatestPreviewContent(realm: Realm, roomId: String) {
+        val roomSummaryEntity = RoomSummaryEntity.getOrNull(realm, roomId)
+        if (roomSummaryEntity != null) {
+            val latestPreviewableEvent = RoomSummaryEventsHelper.getLatestPreviewableEvent(realm, roomId)
+            latestPreviewableEvent?.attemptToDecrypt()
+        }
+    }
 
     fun update(realm: Realm,
                roomId: String,
@@ -128,6 +135,7 @@ internal class RoomSummaryUpdater @Inject constructor(
         val lastActivityFromEvent = latestPreviewableEvent?.root?.originServerTs
         if (lastActivityFromEvent != null) {
             roomSummaryEntity.lastActivityTime = lastActivityFromEvent
+            latestPreviewableEvent.attemptToDecrypt()
         }
 
         roomSummaryEntity.hasUnreadMessages = roomSummaryEntity.notificationCount > 0 ||
@@ -161,18 +169,6 @@ internal class RoomSummaryUpdater @Inject constructor(
         }
         roomSummaryEntity.updateHasFailedSending()
 
-        val root = latestPreviewableEvent?.root
-        if (root?.type == EventType.ENCRYPTED && root.decryptionResultJson == null) {
-            Timber.v("Should decrypt ${latestPreviewableEvent.eventId}")
-            // mmm i want to decrypt now or is it ok to do it async?
-            tryOrNull {
-                runBlocking {
-                    eventDecryptor.decryptEvent(root.asDomain(), "")
-                }
-            }
-                    ?.let { root.setDecryptionResult(it) }
-        }
-
         if (updateMembers) {
             val otherRoomMembers = RoomMemberHelper(realm, roomId)
                     .queryActiveRoomMembersEvent()
@@ -189,6 +185,22 @@ internal class RoomSummaryUpdater @Inject constructor(
         }
     }
 
+    private fun TimelineEventEntity.attemptToDecrypt() {
+        when (val root = this.root) {
+            null -> {
+                Timber.v("Decryption skipped due to missing root event $eventId")
+            }
+            else -> {
+                if (root.type == EventType.ENCRYPTED && root.decryptionResultJson == null) {
+                    Timber.v("Should decrypt $eventId")
+                    tryOrNull {
+                        runBlocking { eventDecryptor.decryptEvent(root.asDomain(), "") }
+                    }?.let { root.setDecryptionResult(it) }
+                }
+            }
+        }
+    }
+
     private fun RoomSummaryEntity.updateHasFailedSending() {
         hasFailedSending = TimelineEventEntity.findAllInRoomWithSendStates(realm, roomId, SendState.HAS_FAILED_STATES).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 8c2b4d2bbe..08b2700a43 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
@@ -35,7 +35,7 @@ import org.matrix.android.sdk.api.extensions.tryOrNull
 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.internal.database.lightweight.LightweightSettingsStorage
+import org.matrix.android.sdk.api.settings.LightweightSettingsStorage
 import org.matrix.android.sdk.internal.database.mapper.TimelineEventMapper
 import org.matrix.android.sdk.internal.session.room.membership.LoadRoomMembersTask
 import org.matrix.android.sdk.internal.session.room.relation.threads.FetchThreadTimelineTask
@@ -100,6 +100,7 @@ internal class DefaultTimeline(private val roomId: String,
             threadsAwarenessHandler = threadsAwarenessHandler,
             lightweightSettingsStorage = lightweightSettingsStorage,
             onEventsUpdated = this::sendSignalToPostSnapshot,
+            onEventsDeleted = this::onEventsDeleted,
             onLimitedTimeline = this::onLimitedTimeline,
             onNewTimelineEvents = this::onNewTimelineEvents
     )
@@ -223,7 +224,15 @@ internal class DefaultTimeline(private val roomId: String,
         updateState(direction) {
             it.copy(loading = true)
         }
-        val loadMoreResult = strategy.loadMore(count, direction, fetchOnServerIfNeeded)
+        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
+        }
         Timber.v("$baseLogMessage: result $loadMoreResult")
         val hasMoreToLoad = loadMoreResult != LoadMoreResult.REACHED_END
         updateState(direction) {
@@ -296,6 +305,12 @@ internal class DefaultTimeline(private val roomId: String,
         }
     }
 
+    private fun onEventsDeleted() {
+        // Some event have been deleted, for instance when a user has been ignored.
+        // Restart the timeline (live)
+        restartWithEventId(null)
+    }
+
     private suspend fun postSnapshot() {
         val snapshot = strategy.buildSnapshot()
         Timber.v("Post snapshot of ${snapshot.size} events")
@@ -342,6 +357,14 @@ internal class DefaultTimeline(private val roomId: String,
         }
     }
 
+    private fun onTimelineFailure(throwable: Throwable) {
+        timelineScope.launch(coroutineDispatchers.main) {
+            listeners.forEach {
+                tryOrNull { it.onTimelineFailure(throwable) }
+            }
+        }
+    }
+
     private fun buildStrategy(mode: LoadTimelineStrategy.Mode): LoadTimelineStrategy {
         return LoadTimelineStrategy(
                 roomId = roomId,
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/DefaultTimelineService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/DefaultTimelineService.kt
index 1ba2aff191..826c9d7c48 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/DefaultTimelineService.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/DefaultTimelineService.kt
@@ -26,8 +26,8 @@ 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.TimelineService
 import org.matrix.android.sdk.api.session.room.timeline.TimelineSettings
+import org.matrix.android.sdk.api.settings.LightweightSettingsStorage
 import org.matrix.android.sdk.api.util.Optional
-import org.matrix.android.sdk.internal.database.lightweight.LightweightSettingsStorage
 import org.matrix.android.sdk.internal.database.mapper.TimelineEventMapper
 import org.matrix.android.sdk.internal.di.SessionDatabase
 import org.matrix.android.sdk.internal.session.room.membership.LoadRoomMembersTask
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 9ede2f6562..5bca5118b8 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
@@ -17,9 +17,9 @@
 package org.matrix.android.sdk.internal.session.room.timeline
 
 import org.matrix.android.sdk.api.extensions.tryOrNull
+import org.matrix.android.sdk.api.session.crypto.model.OlmDecryptionResult
 import org.matrix.android.sdk.api.session.events.model.Event
 import org.matrix.android.sdk.internal.crypto.EventDecryptor
-import org.matrix.android.sdk.internal.crypto.algorithms.olm.OlmDecryptionResult
 import org.matrix.android.sdk.internal.network.GlobalErrorReceiver
 import org.matrix.android.sdk.internal.network.executeRequest
 import org.matrix.android.sdk.internal.session.room.RoomAPI
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/LiveTimelineEvent.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/LiveTimelineEvent.kt
index 64b1a4ff1d..e765e05578 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/LiveTimelineEvent.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/LiveTimelineEvent.kt
@@ -41,7 +41,7 @@ internal class LiveTimelineEvent(private val monarchy: Monarchy,
                                  private val timelineEventMapper: TimelineEventMapper,
                                  private val roomId: String,
                                  private val eventId: String) :
-    MediatorLiveData>() {
+        MediatorLiveData>() {
 
     init {
         buildAndObserveQuery()
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/LoadTimelineStrategy.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/LoadTimelineStrategy.kt
index a9e7b3bcdc..8819ffe69f 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
@@ -24,12 +24,14 @@ import io.realm.RealmResults
 import io.realm.kotlin.createObject
 import kotlinx.coroutines.CompletableDeferred
 import org.matrix.android.sdk.api.extensions.orFalse
+import org.matrix.android.sdk.api.failure.Failure
+import org.matrix.android.sdk.api.failure.MatrixError
 import org.matrix.android.sdk.api.session.room.send.SendState
 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.api.settings.LightweightSettingsStorage
 import org.matrix.android.sdk.internal.database.helper.addIfNecessary
-import org.matrix.android.sdk.internal.database.lightweight.LightweightSettingsStorage
 import org.matrix.android.sdk.internal.database.mapper.TimelineEventMapper
 import org.matrix.android.sdk.internal.database.model.ChunkEntity
 import org.matrix.android.sdk.internal.database.model.ChunkEntityFields
@@ -93,6 +95,7 @@ internal class LoadTimelineStrategy(
             val threadsAwarenessHandler: ThreadsAwarenessHandler,
             val lightweightSettingsStorage: LightweightSettingsStorage,
             val onEventsUpdated: (Boolean) -> Unit,
+            val onEventsDeleted: () -> Unit,
             val onLimitedTimeline: () -> Unit,
             val onNewTimelineEvents: (List) -> Unit
     )
@@ -194,6 +197,10 @@ internal class LoadTimelineStrategy(
                 getContextLatch?.await()
                 getContextLatch = null
             } catch (failure: Throwable) {
+                if (failure is Failure.ServerError && failure.error.code in listOf(MatrixError.M_NOT_FOUND, MatrixError.M_FORBIDDEN)) {
+                    // This failure is likely permanent, so handle in DefaultTimeline to restart without eventId
+                    throw failure
+                }
                 return LoadMoreResult.FAILURE
             }
         }
@@ -296,7 +303,8 @@ internal class LoadTimelineStrategy(
                     threadsAwarenessHandler = dependencies.threadsAwarenessHandler,
                     lightweightSettingsStorage = dependencies.lightweightSettingsStorage,
                     initialEventId = mode.originEventId(),
-                    onBuiltEvents = dependencies.onEventsUpdated
+                    onBuiltEvents = dependencies.onEventsUpdated,
+                    onEventsDeleted = dependencies.onEventsDeleted,
             )
         }
     }
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/SendingEventsDataSource.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/SendingEventsDataSource.kt
index 1262c09d97..637267a9b1 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/SendingEventsDataSource.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/SendingEventsDataSource.kt
@@ -45,9 +45,11 @@ internal class RealmSendingEventsDataSource(
     private var frozenSendingTimelineEvents: RealmList? = null
 
     private val sendingTimelineEventsListener = RealmChangeListener> { events ->
-        uiEchoManager.onSentEventsInDatabase(events.map { it.eventId })
-        updateFrozenResults(events)
-        onEventsUpdated(false)
+        if (events.isValid) {
+            uiEchoManager.onSentEventsInDatabase(events.map { it.eventId })
+            updateFrozenResults(events)
+            onEventsUpdated(false)
+        }
     }
 
     override fun start() {
@@ -55,6 +57,7 @@ internal class RealmSendingEventsDataSource(
         roomEntity = RoomEntity.where(safeRealm, roomId = roomId).findFirst()
         sendingTimelineEvents = roomEntity?.sendingTimelineEvents
         sendingTimelineEvents?.addChangeListener(sendingTimelineEventsListener)
+        updateFrozenResults(sendingTimelineEvents)
     }
 
     override fun stop() {
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 8a7078fdf9..27f4245b22 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/TimelineChunk.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/TimelineChunk.kt
@@ -30,7 +30,7 @@ import org.matrix.android.sdk.api.session.events.model.EventType
 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.internal.database.lightweight.LightweightSettingsStorage
+import org.matrix.android.sdk.api.settings.LightweightSettingsStorage
 import org.matrix.android.sdk.internal.database.mapper.EventMapper
 import org.matrix.android.sdk.internal.database.mapper.TimelineEventMapper
 import org.matrix.android.sdk.internal.database.model.ChunkEntity
@@ -49,21 +49,24 @@ import java.util.concurrent.atomic.AtomicBoolean
  * It does mainly listen to the db timeline events.
  * It also triggers pagination to the server when needed, or dispatch to the prev or next chunk if any.
  */
-internal class TimelineChunk(private val chunkEntity: ChunkEntity,
-                             private val timelineSettings: TimelineSettings,
-                             private val roomId: String,
-                             private val timelineId: String,
-                             private val fetchThreadTimelineTask: FetchThreadTimelineTask,
-                             private val eventDecryptor: TimelineEventDecryptor,
-                             private val paginationTask: PaginationTask,
-                             private val realmConfiguration: RealmConfiguration,
-                             private val fetchTokenAndPaginateTask: FetchTokenAndPaginateTask,
-                             private val timelineEventMapper: TimelineEventMapper,
-                             private val uiEchoManager: UIEchoManager? = null,
-                             private val threadsAwarenessHandler: ThreadsAwarenessHandler,
-                             private val lightweightSettingsStorage: LightweightSettingsStorage,
-                             private val initialEventId: String?,
-                             private val onBuiltEvents: (Boolean) -> Unit) {
+internal class TimelineChunk(
+        private val chunkEntity: ChunkEntity,
+        private val timelineSettings: TimelineSettings,
+        private val roomId: String,
+        private val timelineId: String,
+        private val fetchThreadTimelineTask: FetchThreadTimelineTask,
+        private val eventDecryptor: TimelineEventDecryptor,
+        private val paginationTask: PaginationTask,
+        private val realmConfiguration: RealmConfiguration,
+        private val fetchTokenAndPaginateTask: FetchTokenAndPaginateTask,
+        private val timelineEventMapper: TimelineEventMapper,
+        private val uiEchoManager: UIEchoManager?,
+        private val threadsAwarenessHandler: ThreadsAwarenessHandler,
+        private val lightweightSettingsStorage: LightweightSettingsStorage,
+        private val initialEventId: String?,
+        private val onBuiltEvents: (Boolean) -> Unit,
+        private val onEventsDeleted: () -> Unit,
+) {
 
     private val isLastForward = AtomicBoolean(chunkEntity.isLastForward)
     private val isLastBackward = AtomicBoolean(chunkEntity.isLastBackward)
@@ -83,11 +86,15 @@ internal class TimelineChunk(private val chunkEntity: ChunkEntity,
             isLastBackward.set(chunkEntity.isLastBackward)
         }
         if (changeSet.isFieldChanged(ChunkEntityFields.NEXT_CHUNK.`$`)) {
-            nextChunk = createTimelineChunk(chunkEntity.nextChunk)
+            nextChunk = createTimelineChunk(chunkEntity.nextChunk).also {
+                it?.prevChunk = this
+            }
             nextChunkLatch?.complete(Unit)
         }
         if (changeSet.isFieldChanged(ChunkEntityFields.PREV_CHUNK.`$`)) {
-            prevChunk = createTimelineChunk(chunkEntity.prevChunk)
+            prevChunk = createTimelineChunk(chunkEntity.prevChunk).also {
+                it?.nextChunk = this
+            }
             prevChunkLatch?.complete(Unit)
         }
     }
@@ -194,7 +201,9 @@ internal class TimelineChunk(private val chunkEntity: ChunkEntity,
             when {
                 nextChunkEntity != null -> {
                     if (nextChunk == null) {
-                        nextChunk = createTimelineChunk(nextChunkEntity)
+                        nextChunk = createTimelineChunk(nextChunkEntity).also {
+                            it?.prevChunk = this
+                        }
                     }
                     nextChunk?.loadMore(offsetCount, direction, fetchFromServerIfNeeded) ?: LoadMoreResult.FAILURE
                 }
@@ -210,7 +219,9 @@ internal class TimelineChunk(private val chunkEntity: ChunkEntity,
             when {
                 prevChunkEntity != null -> {
                     if (prevChunk == null) {
-                        prevChunk = createTimelineChunk(prevChunkEntity)
+                        prevChunk = createTimelineChunk(prevChunkEntity).also {
+                            it?.nextChunk = this
+                        }
                     }
                     prevChunk?.loadMore(offsetCount, direction, fetchFromServerIfNeeded) ?: LoadMoreResult.FAILURE
                 }
@@ -497,6 +508,11 @@ internal class TimelineChunk(private val chunkEntity: ChunkEntity,
         if (insertions.isNotEmpty() || modifications.isNotEmpty()) {
             onBuiltEvents(true)
         }
+
+        val deletions = changeSet.deletions
+        if (deletions.isNotEmpty()) {
+            onEventsDeleted()
+        }
     }
 
     private fun getNextDisplayIndex(direction: Timeline.Direction): Int? {
@@ -535,7 +551,8 @@ internal class TimelineChunk(private val chunkEntity: ChunkEntity,
                 threadsAwarenessHandler = threadsAwarenessHandler,
                 lightweightSettingsStorage = lightweightSettingsStorage,
                 initialEventId = null,
-                onBuiltEvents = this.onBuiltEvents
+                onBuiltEvents = this.onBuiltEvents,
+                onEventsDeleted = this.onEventsDeleted
         )
     }
 
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/TimelineEventDataSource.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/TimelineEventDataSource.kt
index 638866a46e..8b58d3ca5c 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/TimelineEventDataSource.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/TimelineEventDataSource.kt
@@ -19,7 +19,6 @@ package org.matrix.android.sdk.internal.session.room.timeline
 import androidx.lifecycle.LiveData
 import com.zhuinden.monarchy.Monarchy
 import io.realm.Sort
-import io.realm.kotlin.where
 import org.matrix.android.sdk.api.session.events.model.isImageMessage
 import org.matrix.android.sdk.api.session.events.model.isVideoMessage
 import org.matrix.android.sdk.api.session.room.timeline.TimelineEvent
@@ -29,6 +28,7 @@ import org.matrix.android.sdk.internal.database.mapper.TimelineEventMapper
 import org.matrix.android.sdk.internal.database.model.TimelineEventEntity
 import org.matrix.android.sdk.internal.database.model.TimelineEventEntityFields
 import org.matrix.android.sdk.internal.database.query.where
+import org.matrix.android.sdk.internal.database.query.whereRoomId
 import org.matrix.android.sdk.internal.di.SessionDatabase
 import org.matrix.android.sdk.internal.task.TaskExecutor
 import javax.inject.Inject
@@ -53,8 +53,7 @@ internal class TimelineEventDataSource @Inject constructor(private val realmSess
     fun getAttachmentMessages(roomId: String): List {
         // TODO pretty bad query.. maybe we should denormalize clear type in base?
         return realmSessionProvider.withRealm { realm ->
-            realm.where()
-                    .equalTo(TimelineEventEntityFields.ROOM_ID, roomId)
+            TimelineEventEntity.whereRoomId(realm, roomId)
                     .sort(TimelineEventEntityFields.DISPLAY_INDEX, Sort.ASCENDING)
                     .findAll()
                     ?.mapNotNull { timelineEventMapper.map(it).takeIf { it.root.isImageMessage() || it.root.isVideoMessage() } }
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/TimelineEventDecryptor.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/TimelineEventDecryptor.kt
index e225b31f45..41e173a5db 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/TimelineEventDecryptor.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/TimelineEventDecryptor.kt
@@ -20,11 +20,10 @@ import io.realm.RealmConfiguration
 import kotlinx.coroutines.runBlocking
 import org.matrix.android.sdk.api.session.crypto.CryptoService
 import org.matrix.android.sdk.api.session.crypto.MXCryptoError
+import org.matrix.android.sdk.api.session.crypto.NewSessionListener
 import org.matrix.android.sdk.api.session.events.model.Event
+import org.matrix.android.sdk.api.session.events.model.content.EncryptedEventContent
 import org.matrix.android.sdk.api.session.events.model.toModel
-import org.matrix.android.sdk.internal.crypto.NewSessionListener
-import org.matrix.android.sdk.internal.crypto.model.event.EncryptedEventContent
-import org.matrix.android.sdk.internal.database.lightweight.LightweightSettingsStorage
 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.query.where
@@ -40,7 +39,6 @@ internal class TimelineEventDecryptor @Inject constructor(
         private val realmConfiguration: RealmConfiguration,
         private val cryptoService: CryptoService,
         private val threadsAwarenessHandler: ThreadsAwarenessHandler,
-        private val lightweightSettingsStorage: LightweightSettingsStorage
 ) {
 
     private val newSessionListener = object : NewSessionListener {
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 63383a99b3..d3f24a8568 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
@@ -23,11 +23,11 @@ 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.RoomMemberContent
 import org.matrix.android.sdk.api.session.room.send.SendState
+import org.matrix.android.sdk.api.settings.LightweightSettingsStorage
 import org.matrix.android.sdk.internal.database.helper.addIfNecessary
 import org.matrix.android.sdk.internal.database.helper.addStateEvent
 import org.matrix.android.sdk.internal.database.helper.addTimelineEvent
 import org.matrix.android.sdk.internal.database.helper.updateThreadSummaryIfNeeded
-import org.matrix.android.sdk.internal.database.lightweight.LightweightSettingsStorage
 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.EventEntity
@@ -38,6 +38,7 @@ 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.where
 import org.matrix.android.sdk.internal.di.SessionDatabase
 import org.matrix.android.sdk.internal.di.UserId
@@ -80,7 +81,8 @@ internal class TokenChunkEventPersistor @Inject constructor(
 
                     val existingChunk = ChunkEntity.find(realm, roomId, prevToken = prevToken, nextToken = nextToken)
                     if (existingChunk != null) {
-                        Timber.v("This chunk is already in the db, returns")
+                        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)
                         return@awaitTransaction
                     }
                     val prevChunk = ChunkEntity.find(realm, roomId, nextToken = prevToken)
@@ -89,8 +91,14 @@ internal class TokenChunkEventPersistor @Inject constructor(
                         this.nextChunk = nextChunk
                         this.prevChunk = prevChunk
                     }
-                    nextChunk?.prevChunk = currentChunk
-                    prevChunk?.nextChunk = currentChunk
+                    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
+                    }
                     if (receivedChunk.events.isEmpty() && !receivedChunk.hasMore()) {
                         handleReachEnd(roomId, direction, currentChunk)
                     } else {
@@ -109,6 +117,34 @@ 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")
         if (direction == PaginationDirection.FORWARDS) {
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/typing/TypingBody.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/typing/TypingBody.kt
index 973870bb47..66913e0cbf 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/typing/TypingBody.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/typing/TypingBody.kt
@@ -20,7 +20,7 @@ import com.squareup.moshi.Json
 import com.squareup.moshi.JsonClass
 
 @JsonClass(generateAdapter = true)
-data class TypingBody(
+internal data class TypingBody(
         // Required. Whether the user is typing or not. If false, the timeout key can be omitted.
         @Json(name = "typing")
         val typing: Boolean,
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/typing/TypingEventContent.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/typing/TypingEventContent.kt
index 488d38d762..5b01085648 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/typing/TypingEventContent.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/typing/TypingEventContent.kt
@@ -20,7 +20,7 @@ import com.squareup.moshi.Json
 import com.squareup.moshi.JsonClass
 
 @JsonClass(generateAdapter = true)
-data class TypingEventContent(
+internal data class TypingEventContent(
         @Json(name = "user_ids")
         val typingUserIds: List = emptyList()
 )
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/uploads/GetUploadsTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/uploads/GetUploadsTask.kt
index 028c3e9193..7daf506c14 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/uploads/GetUploadsTask.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/uploads/GetUploadsTask.kt
@@ -17,6 +17,7 @@
 package org.matrix.android.sdk.internal.session.room.uploads
 
 import com.zhuinden.monarchy.Monarchy
+import io.realm.Sort
 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
@@ -75,6 +76,7 @@ internal class DefaultGetUploadsTask @Inject constructor(
             monarchy.doWithRealm { realm ->
                 eventsFromRealm = EventEntity.whereType(realm, EventType.ENCRYPTED, params.roomId)
                         .like(EventEntityFields.DECRYPTION_RESULT_JSON, TimelineEventFilter.DecryptedContent.URL)
+                        .sort(EventEntityFields.ORIGIN_SERVER_TS, Sort.DESCENDING)
                         .findAll()
                         .map { it.asDomain() }
                         // Exclude stickers
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/search/SearchTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/search/SearchTask.kt
index 3ba7d11c3d..fcaf3b60a7 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/search/SearchTask.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/search/SearchTask.kt
@@ -87,7 +87,7 @@ internal class DefaultSearchTask @Inject constructor(
                 results = searchCategories.roomEvents?.results?.map { searchResponseItem ->
 
                     val localThreadEventDetails = localTimelineEvents
-                            ?.firstOrNull { it.eventId ==  searchResponseItem.event.eventId }
+                            ?.firstOrNull { it.eventId == searchResponseItem.event.eventId }
                             ?.root
                             ?.asDomain()
                             ?.threadDetails
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/securestorage/SecretStoringUtils.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/securestorage/SecretStoringUtils.kt
index fad1840e51..17dc90fdb0 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/securestorage/SecretStoringUtils.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/securestorage/SecretStoringUtils.kt
@@ -34,7 +34,6 @@ import java.io.InputStream
 import java.io.ObjectInputStream
 import java.io.ObjectOutputStream
 import java.io.OutputStream
-import java.lang.IllegalArgumentException
 import java.math.BigInteger
 import java.security.KeyPairGenerator
 import java.security.KeyStore
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 e764ab551a..355039b22c 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/space/DefaultSpaceService.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/space/DefaultSpaceService.kt
@@ -18,6 +18,8 @@ package org.matrix.android.sdk.internal.session.space
 
 import android.net.Uri
 import androidx.lifecycle.LiveData
+import kotlinx.coroutines.withContext
+import org.matrix.android.sdk.api.MatrixCoroutineDispatchers
 import org.matrix.android.sdk.api.query.QueryStringValue
 import org.matrix.android.sdk.api.session.events.model.Event
 import org.matrix.android.sdk.api.session.events.model.EventType
@@ -43,6 +45,7 @@ import org.matrix.android.sdk.api.session.space.SpaceService
 import org.matrix.android.sdk.api.session.space.SpaceSummaryQueryParams
 import org.matrix.android.sdk.api.session.space.model.SpaceChildContent
 import org.matrix.android.sdk.api.session.space.model.SpaceParentContent
+import org.matrix.android.sdk.api.session.space.peeking.SpacePeekResult
 import org.matrix.android.sdk.internal.di.UserId
 import org.matrix.android.sdk.internal.session.room.RoomGetter
 import org.matrix.android.sdk.internal.session.room.SpaceGetter
@@ -51,7 +54,6 @@ import org.matrix.android.sdk.internal.session.room.membership.leaving.LeaveRoom
 import org.matrix.android.sdk.internal.session.room.state.StateEventDataSource
 import org.matrix.android.sdk.internal.session.room.summary.RoomSummaryDataSource
 import org.matrix.android.sdk.internal.session.space.peeking.PeekSpaceTask
-import org.matrix.android.sdk.internal.session.space.peeking.SpacePeekResult
 import javax.inject.Inject
 
 internal class DefaultSpaceService @Inject constructor(
@@ -64,7 +66,8 @@ internal class DefaultSpaceService @Inject constructor(
         private val stateEventDataSource: StateEventDataSource,
         private val peekSpaceTask: PeekSpaceTask,
         private val resolveSpaceInfoTask: ResolveSpaceInfoTask,
-        private val leaveRoomTask: LeaveRoomTask
+        private val leaveRoomTask: LeaveRoomTask,
+        private val coroutineDispatchers: MatrixCoroutineDispatchers,
 ) : SpaceService {
 
     override suspend fun createSpace(params: CreateSpaceParams): String {
@@ -105,8 +108,10 @@ internal class DefaultSpaceService @Inject constructor(
         return roomSummaryDataSource.getSpaceSummaries(spaceSummaryQueryParams, sortOrder)
     }
 
-    override fun getRootSpaceSummaries(): List {
-        return roomSummaryDataSource.getRootSpaceSummaries()
+    override suspend fun getRootSpaceSummaries(): List {
+        return withContext(coroutineDispatchers.io) {
+            roomSummaryDataSource.getRootSpaceSummaries()
+        }
     }
 
     override suspend fun peekSpace(spaceId: String): SpacePeekResult {
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/space/peeking/PeekSpaceTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/space/peeking/PeekSpaceTask.kt
index c45d4420ae..3647941998 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/space/peeking/PeekSpaceTask.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/space/peeking/PeekSpaceTask.kt
@@ -23,6 +23,11 @@ import org.matrix.android.sdk.api.session.room.model.RoomType
 import org.matrix.android.sdk.api.session.room.model.create.RoomCreateContent
 import org.matrix.android.sdk.api.session.room.peeking.PeekResult
 import org.matrix.android.sdk.api.session.space.model.SpaceChildContent
+import org.matrix.android.sdk.api.session.space.peeking.ISpaceChild
+import org.matrix.android.sdk.api.session.space.peeking.SpaceChildPeekResult
+import org.matrix.android.sdk.api.session.space.peeking.SpacePeekResult
+import org.matrix.android.sdk.api.session.space.peeking.SpacePeekSummary
+import org.matrix.android.sdk.api.session.space.peeking.SpaceSubChildPeekResult
 import org.matrix.android.sdk.internal.session.room.peeking.PeekRoomTask
 import org.matrix.android.sdk.internal.session.room.peeking.ResolveRoomStateTask
 import org.matrix.android.sdk.internal.task.Task
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/RoomSyncEphemeralTemporaryStore.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/RoomSyncEphemeralTemporaryStore.kt
index 8c68e224dc..ef9f468c86 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/RoomSyncEphemeralTemporaryStore.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/RoomSyncEphemeralTemporaryStore.kt
@@ -21,8 +21,8 @@ import com.squareup.moshi.Moshi
 import okio.buffer
 import okio.source
 import org.matrix.android.sdk.api.session.sync.model.RoomSyncEphemeral
+import org.matrix.android.sdk.api.util.md5
 import org.matrix.android.sdk.internal.di.SessionFilesDirectory
-import org.matrix.android.sdk.internal.util.md5
 import timber.log.Timber
 import java.io.File
 import javax.inject.Inject
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 18e17c7d13..42cd972e0c 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/SyncPresence.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/SyncPresence.kt
@@ -16,6 +16,8 @@
 
 package org.matrix.android.sdk.internal.session.sync
 
+import org.matrix.android.sdk.api.session.presence.model.PresenceEnum
+
 /**
  * For `set_presence` parameter in the /sync request
  *
@@ -24,8 +26,20 @@ package org.matrix.android.sdk.internal.session.sync
  * parameter is set to "offline" then the client is not marked as being online when it uses this API.
  * When set to "unavailable", the client is marked as being idle. One of: ["offline", "online", "unavailable"]
  */
-enum class SyncPresence(val value: String) {
+internal enum class SyncPresence(val value: String) {
     Offline("offline"),
     Online("online"),
-    Unavailable("unavailable")
+    Unavailable("unavailable");
+
+    companion object {
+        fun from(presenceEnum: PresenceEnum): SyncPresence {
+            return when (presenceEnum) {
+                PresenceEnum.ONLINE      -> Online
+                PresenceEnum.OFFLINE     -> Offline
+                PresenceEnum.UNAVAILABLE -> Unavailable
+            }
+        }
+
+        fun from(s: String?): SyncPresence? = values().find { it.value == s }
+    }
 }
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/SyncResponseHandler.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/SyncResponseHandler.kt
index 1bbf54a788..02a7a9a37f 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/SyncResponseHandler.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/SyncResponseHandler.kt
@@ -18,15 +18,14 @@ package org.matrix.android.sdk.internal.session.sync
 
 import androidx.work.ExistingPeriodicWorkPolicy
 import com.zhuinden.monarchy.Monarchy
-import org.matrix.android.sdk.api.pushrules.PushRuleService
-import org.matrix.android.sdk.api.pushrules.RuleScope
 import org.matrix.android.sdk.api.session.initsync.InitSyncStep
+import org.matrix.android.sdk.api.session.pushrules.PushRuleService
+import org.matrix.android.sdk.api.session.pushrules.RuleScope
 import org.matrix.android.sdk.api.session.sync.model.GroupsSyncResponse
 import org.matrix.android.sdk.api.session.sync.model.RoomsSyncResponse
 import org.matrix.android.sdk.api.session.sync.model.SyncResponse
 import org.matrix.android.sdk.internal.SessionManager
 import org.matrix.android.sdk.internal.crypto.DefaultCryptoService
-import org.matrix.android.sdk.internal.database.lightweight.LightweightSettingsStorage
 import org.matrix.android.sdk.internal.di.SessionDatabase
 import org.matrix.android.sdk.internal.di.SessionId
 import org.matrix.android.sdk.internal.di.WorkManagerProvider
@@ -35,14 +34,13 @@ import org.matrix.android.sdk.internal.session.dispatchTo
 import org.matrix.android.sdk.internal.session.group.GetGroupDataWorker
 import org.matrix.android.sdk.internal.session.initsync.ProgressReporter
 import org.matrix.android.sdk.internal.session.initsync.reportSubtask
-import org.matrix.android.sdk.internal.session.notification.ProcessEventForPushTask
+import org.matrix.android.sdk.internal.session.pushrules.ProcessEventForPushTask
 import org.matrix.android.sdk.internal.session.sync.handler.CryptoSyncHandler
 import org.matrix.android.sdk.internal.session.sync.handler.GroupSyncHandler
 import org.matrix.android.sdk.internal.session.sync.handler.PresenceSyncHandler
 import org.matrix.android.sdk.internal.session.sync.handler.SyncResponsePostTreatmentAggregatorHandler
 import org.matrix.android.sdk.internal.session.sync.handler.UserAccountDataSyncHandler
 import org.matrix.android.sdk.internal.session.sync.handler.room.RoomSyncHandler
-import org.matrix.android.sdk.internal.session.sync.handler.room.ThreadsAwarenessHandler
 import org.matrix.android.sdk.internal.util.awaitTransaction
 import org.matrix.android.sdk.internal.worker.WorkerParamsFactory
 import timber.log.Timber
@@ -65,10 +63,8 @@ internal class SyncResponseHandler @Inject constructor(
         private val aggregatorHandler: SyncResponsePostTreatmentAggregatorHandler,
         private val cryptoService: DefaultCryptoService,
         private val tokenStore: SyncTokenStore,
-        private val lightweightSettingsStorage: LightweightSettingsStorage,
         private val processEventForPushTask: ProcessEventForPushTask,
         private val pushRuleService: PushRuleService,
-        private val threadsAwarenessHandler: ThreadsAwarenessHandler,
         private val presenceSyncHandler: PresenceSyncHandler
 ) {
 
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/SyncResponsePostTreatmentAggregator.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/SyncResponsePostTreatmentAggregator.kt
index fe44531390..e9452c59fc 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/SyncResponsePostTreatmentAggregator.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/SyncResponsePostTreatmentAggregator.kt
@@ -22,4 +22,7 @@ internal class SyncResponsePostTreatmentAggregator {
 
     // Map of roomId to directUserId
     val directChatsToCheck = mutableMapOf()
+
+    // List of userIds to fetch and update at the end of incremental syncs
+    val userIdsToFetch = mutableListOf()
 }
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 b4da1a02cd..197037f1cf 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
@@ -18,11 +18,14 @@ package org.matrix.android.sdk.internal.session.sync
 
 import android.os.SystemClock
 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.InitialSyncStrategy
+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
 import org.matrix.android.sdk.internal.di.SessionFilesDirectory
@@ -104,7 +107,11 @@ internal class DefaultSyncTask @Inject constructor(
         val isInitialSync = token == null
         if (isInitialSync) {
             // We might want to get the user information in parallel too
-            userStore.createOrUpdate(userId)
+            val user = tryOrNull { session.profileService().getProfileAsUser(userId) }
+            userStore.createOrUpdate(
+                    userId = userId,
+                    displayName = user?.displayName,
+                    avatarUrl = user?.avatarUrl)
             defaultSyncStatusService.startRoot(InitSyncStep.ImportingAccount, 100)
         }
         // Maybe refresh the homeserver capabilities data we know
@@ -165,7 +172,7 @@ internal class DefaultSyncTask @Inject constructor(
             val nbToDevice = syncResponse.toDevice?.events.orEmpty().size
             val nextBatch = syncResponse.nextBatch
             Timber.tag(loggerTag.value).d(
-                "Incremental sync request parsing, $nbRooms room(s) $nbToDevice toDevice(s). Got nextBatch: $nextBatch"
+                    "Incremental sync request parsing, $nbRooms room(s) $nbToDevice toDevice(s). Got nextBatch: $nextBatch"
             )
             defaultSyncStatusService.setStatus(SyncStatusService.Status.IncrementalSyncParsing(
                     rooms = nbRooms,
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 9ae7b82777..429f498533 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
@@ -18,16 +18,16 @@ package org.matrix.android.sdk.internal.session.sync.handler
 
 import org.matrix.android.sdk.api.logger.LoggerTag
 import org.matrix.android.sdk.api.session.crypto.MXCryptoError
+import org.matrix.android.sdk.api.session.crypto.model.MXEventDecryptionResult
+import org.matrix.android.sdk.api.session.crypto.model.OlmDecryptionResult
 import org.matrix.android.sdk.api.session.events.model.Event
 import org.matrix.android.sdk.api.session.events.model.EventType
+import org.matrix.android.sdk.api.session.events.model.content.OlmEventContent
 import org.matrix.android.sdk.api.session.events.model.toModel
 import org.matrix.android.sdk.api.session.room.model.message.MessageContent
 import org.matrix.android.sdk.api.session.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.MXEventDecryptionResult
-import org.matrix.android.sdk.internal.crypto.algorithms.olm.OlmDecryptionResult
-import org.matrix.android.sdk.internal.crypto.model.event.OlmEventContent
 import org.matrix.android.sdk.internal.crypto.verification.DefaultVerificationService
 import org.matrix.android.sdk.internal.session.initsync.ProgressReporter
 import timber.log.Timber
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/handler/PresenceSyncHandler.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/handler/PresenceSyncHandler.kt
index e5bed12181..6a7af1dda4 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/handler/PresenceSyncHandler.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/handler/PresenceSyncHandler.kt
@@ -31,26 +31,24 @@ import javax.inject.Inject
 internal class PresenceSyncHandler @Inject constructor(private val matrixConfiguration: MatrixConfiguration) {
 
     fun handle(realm: Realm, presenceSyncResponse: PresenceSyncResponse?) {
-        if (matrixConfiguration.presenceSyncEnabled) {
-            presenceSyncResponse?.events
-                    ?.filter { event -> event.type == EventType.PRESENCE }
-                    ?.forEach { event ->
-                        val content = event.getPresenceContent() ?: return@forEach
-                        val userId = event.senderId ?: return@forEach
-                        val userPresenceEntity = UserPresenceEntity(
-                                userId = userId,
-                                lastActiveAgo = content.lastActiveAgo,
-                                statusMessage = content.statusMessage,
-                                isCurrentlyActive = content.isCurrentlyActive,
-                                avatarUrl = content.avatarUrl,
-                                displayName = content.displayName
-                        ).also {
-                            it.presence = content.presence
-                        }
-
-                        storePresenceToDB(realm, userPresenceEntity)
+        presenceSyncResponse?.events
+                ?.filter { event -> event.type == EventType.PRESENCE }
+                ?.forEach { event ->
+                    val content = event.getPresenceContent() ?: return@forEach
+                    val userId = event.senderId ?: return@forEach
+                    val userPresenceEntity = UserPresenceEntity(
+                            userId = userId,
+                            lastActiveAgo = content.lastActiveAgo,
+                            statusMessage = content.statusMessage,
+                            isCurrentlyActive = content.isCurrentlyActive,
+                            avatarUrl = content.avatarUrl,
+                            displayName = content.displayName
+                    ).also {
+                        it.presence = content.presence
                     }
-        }
+
+                    storePresenceToDB(realm, userPresenceEntity)
+                }
     }
 
     /**
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/handler/SyncResponsePostTreatmentAggregatorHandler.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/handler/SyncResponsePostTreatmentAggregatorHandler.kt
index 1e0e87a450..c638ed4f80 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/handler/SyncResponsePostTreatmentAggregatorHandler.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/handler/SyncResponsePostTreatmentAggregatorHandler.kt
@@ -16,22 +16,32 @@
 
 package org.matrix.android.sdk.internal.session.sync.handler
 
+import com.zhuinden.monarchy.Monarchy
 import org.matrix.android.sdk.api.MatrixPatterns
+import org.matrix.android.sdk.api.extensions.tryOrNull
+import org.matrix.android.sdk.api.session.user.model.User
+import org.matrix.android.sdk.internal.di.SessionDatabase
+import org.matrix.android.sdk.internal.session.profile.GetProfileInfoTask
 import org.matrix.android.sdk.internal.session.sync.RoomSyncEphemeralTemporaryStore
 import org.matrix.android.sdk.internal.session.sync.SyncResponsePostTreatmentAggregator
 import org.matrix.android.sdk.internal.session.sync.model.accountdata.toMutable
+import org.matrix.android.sdk.internal.session.user.UserEntityFactory
 import org.matrix.android.sdk.internal.session.user.accountdata.DirectChatsHelper
 import org.matrix.android.sdk.internal.session.user.accountdata.UpdateUserAccountDataTask
+import org.matrix.android.sdk.internal.util.awaitTransaction
 import javax.inject.Inject
 
 internal class SyncResponsePostTreatmentAggregatorHandler @Inject constructor(
         private val directChatsHelper: DirectChatsHelper,
         private val ephemeralTemporaryStore: RoomSyncEphemeralTemporaryStore,
-        private val updateUserAccountDataTask: UpdateUserAccountDataTask
+        private val updateUserAccountDataTask: UpdateUserAccountDataTask,
+        private val getProfileInfoTask: GetProfileInfoTask,
+        @SessionDatabase private val monarchy: Monarchy,
 ) {
-    suspend fun handle(synResHaResponsePostTreatmentAggregator: SyncResponsePostTreatmentAggregator) {
-        cleanupEphemeralFiles(synResHaResponsePostTreatmentAggregator.ephemeralFilesToDelete)
-        updateDirectUserIds(synResHaResponsePostTreatmentAggregator.directChatsToCheck)
+    suspend fun handle(aggregator: SyncResponsePostTreatmentAggregator) {
+        cleanupEphemeralFiles(aggregator.ephemeralFilesToDelete)
+        updateDirectUserIds(aggregator.directChatsToCheck)
+        fetchAndUpdateUsers(aggregator.userIdsToFetch)
     }
 
     private fun cleanupEphemeralFiles(ephemeralFilesToDelete: List) {
@@ -59,13 +69,33 @@ internal class SyncResponsePostTreatmentAggregatorHandler @Inject constructor(
                         }
 
                 // remove roomId from currentDirectUserId entry
-                hasUpdate = hasUpdate or(directChats[currentDirectUserId]?.remove(roomId) == true)
+                hasUpdate = hasUpdate or (directChats[currentDirectUserId]?.remove(roomId) == true)
                 // remove currentDirectUserId entry if there is no attached room anymore
-                hasUpdate = hasUpdate or(directChats.takeIf { it[currentDirectUserId].isNullOrEmpty() }?.remove(currentDirectUserId) != null)
+                hasUpdate = hasUpdate or (directChats.takeIf { it[currentDirectUserId].isNullOrEmpty() }?.remove(currentDirectUserId) != null)
             }
         }
         if (hasUpdate) {
             updateUserAccountDataTask.execute(UpdateUserAccountDataTask.DirectChatParams(directMessages = directChats))
         }
     }
+
+    private suspend fun fetchAndUpdateUsers(userIdsToFetch: List) {
+        fetchUsers(userIdsToFetch)
+                .takeIf { it.isNotEmpty() }
+                ?.saveLocally()
+    }
+
+    private suspend fun fetchUsers(userIdsToFetch: List) = userIdsToFetch.mapNotNull {
+        tryOrNull {
+            val profileJson = getProfileInfoTask.execute(GetProfileInfoTask.Params(it))
+            User.fromJson(it, profileJson)
+        }
+    }
+
+    private suspend fun List.saveLocally() {
+        val userEntities = map { user -> UserEntityFactory.create(user) }
+        monarchy.awaitTransaction {
+            it.insertOrUpdate(userEntities)
+        }
+    }
 }
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/handler/UserAccountDataSyncHandler.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/handler/UserAccountDataSyncHandler.kt
index 7f80486c70..c213ea4bcf 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/handler/UserAccountDataSyncHandler.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/handler/UserAccountDataSyncHandler.kt
@@ -20,17 +20,19 @@ import com.zhuinden.monarchy.Monarchy
 import io.realm.Realm
 import io.realm.RealmList
 import io.realm.kotlin.where
-import org.matrix.android.sdk.api.pushrules.RuleScope
-import org.matrix.android.sdk.api.pushrules.RuleSetKey
-import org.matrix.android.sdk.api.pushrules.rest.GetPushRulesResponse
+import org.matrix.android.sdk.api.failure.GlobalError
+import org.matrix.android.sdk.api.failure.InitialSyncRequestReason
 import org.matrix.android.sdk.api.session.accountdata.UserAccountDataEvent
 import org.matrix.android.sdk.api.session.accountdata.UserAccountDataTypes
 import org.matrix.android.sdk.api.session.events.model.Content
 import org.matrix.android.sdk.api.session.events.model.toModel
+import org.matrix.android.sdk.api.session.pushrules.RuleScope
+import org.matrix.android.sdk.api.session.pushrules.RuleSetKey
 import org.matrix.android.sdk.api.session.room.model.RoomMemberContent
 import org.matrix.android.sdk.api.session.room.model.RoomSummary
 import org.matrix.android.sdk.api.session.sync.model.InvitedRoomSync
 import org.matrix.android.sdk.api.session.sync.model.UserAccountDataSync
+import org.matrix.android.sdk.internal.SessionManager
 import org.matrix.android.sdk.internal.database.mapper.ContentMapper
 import org.matrix.android.sdk.internal.database.mapper.PushRulesMapper
 import org.matrix.android.sdk.internal.database.mapper.asDomain
@@ -39,14 +41,20 @@ import org.matrix.android.sdk.internal.database.model.IgnoredUserEntity
 import org.matrix.android.sdk.internal.database.model.PushRulesEntity
 import org.matrix.android.sdk.internal.database.model.RoomSummaryEntity
 import org.matrix.android.sdk.internal.database.model.RoomSummaryEntityFields
+import org.matrix.android.sdk.internal.database.model.TimelineEventEntity
 import org.matrix.android.sdk.internal.database.model.UserAccountDataEntity
 import org.matrix.android.sdk.internal.database.model.UserAccountDataEntityFields
 import org.matrix.android.sdk.internal.database.model.deleteOnCascade
+import org.matrix.android.sdk.internal.database.query.findAllFrom
 import org.matrix.android.sdk.internal.database.query.getDirectRooms
 import org.matrix.android.sdk.internal.database.query.getOrCreate
 import org.matrix.android.sdk.internal.database.query.where
 import org.matrix.android.sdk.internal.di.SessionDatabase
+import org.matrix.android.sdk.internal.di.SessionId
 import org.matrix.android.sdk.internal.di.UserId
+import org.matrix.android.sdk.internal.session.SessionListeners
+import org.matrix.android.sdk.internal.session.dispatchTo
+import org.matrix.android.sdk.internal.session.pushers.GetPushRulesResponse
 import org.matrix.android.sdk.internal.session.room.RoomAvatarResolver
 import org.matrix.android.sdk.internal.session.room.membership.RoomDisplayNameResolver
 import org.matrix.android.sdk.internal.session.room.membership.RoomMemberHelper
@@ -65,7 +73,10 @@ internal class UserAccountDataSyncHandler @Inject constructor(
         private val directChatsHelper: DirectChatsHelper,
         private val updateUserAccountDataTask: UpdateUserAccountDataTask,
         private val roomAvatarResolver: RoomAvatarResolver,
-        private val roomDisplayNameResolver: RoomDisplayNameResolver
+        private val roomDisplayNameResolver: RoomDisplayNameResolver,
+        @SessionId private val sessionId: String,
+        private val sessionManager: SessionManager,
+        private val sessionListeners: SessionListeners
 ) {
 
     fun handle(realm: Realm, accountData: UserAccountDataSync?) {
@@ -184,12 +195,36 @@ internal class UserAccountDataSyncHandler @Inject constructor(
 
     private fun handleIgnoredUsers(realm: Realm, event: UserAccountDataEvent) {
         val userIds = event.content.toModel()?.ignoredUsers?.keys ?: return
-        realm.where(IgnoredUserEntity::class.java)
-                .findAll()
-                .deleteAllFromRealm()
+        val currentIgnoredUsers = realm.where(IgnoredUserEntity::class.java).findAll()
+        val currentIgnoredUserIds = currentIgnoredUsers.map { it.userId }
+        // Delete the previous list
+        currentIgnoredUsers.deleteAllFromRealm()
         // And save the new received list
         userIds.forEach { realm.createObject(IgnoredUserEntity::class.java).apply { userId = it } }
-        // TODO If not initial sync, we should execute a init sync
+
+        // Delete all the TimelineEvents for all the ignored users
+        // See https://spec.matrix.org/latest/client-server-api/#client-behaviour-22 :
+        // "Once ignored, the client will no longer receive events sent by that user, with the exception of state events"
+        // So just delete all non-state events from our local storage.
+        TimelineEventEntity.findAllFrom(realm, userIds)
+                .also { Timber.d("Deleting ${it.size} TimelineEventEntity from ignored users") }
+                .forEach {
+                    it.deleteOnCascade(true)
+                }
+
+        // Handle the case when some users are unignored from another session
+        val mustRefreshCache = currentIgnoredUserIds.any { currentIgnoredUserId -> currentIgnoredUserId !in userIds }
+        if (mustRefreshCache) {
+            Timber.d("A user has been unignored from another session, an initial sync should be performed")
+            dispatchMustRefresh()
+        }
+    }
+
+    private fun dispatchMustRefresh() {
+        val session = sessionManager.getSessionComponent(sessionId)?.session()
+        session.dispatchTo(sessionListeners) { safeSession, listener ->
+            listener.onGlobalError(safeSession, GlobalError.InitialSyncRequest(InitialSyncRequestReason.IGNORED_USERS_LIST_CHANGE))
+        }
     }
 
     private fun handleBreadcrumbs(realm: Realm, event: UserAccountDataEvent) {
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/handler/room/ReadReceiptHandler.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/handler/room/ReadReceiptHandler.kt
index 025ee329f8..2c84f1e692 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
@@ -33,7 +33,7 @@ import javax.inject.Inject
 // value : dict key $UserId
 //              value dict key ts
 //                    dict value ts value
-typealias ReadReceiptContent = Map>>>
+internal typealias ReadReceiptContent = Map>>>
 
 private const val READ_KEY = "m.read"
 private const val TIMESTAMP_KEY = "ts"
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 e488fd0671..6f8e0bbbb8 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
@@ -20,7 +20,9 @@ import dagger.Lazy
 import io.realm.Realm
 import io.realm.kotlin.createObject
 import kotlinx.coroutines.runBlocking
+import org.matrix.android.sdk.api.crypto.MXCRYPTO_ALGORITHM_MEGOLM
 import org.matrix.android.sdk.api.session.crypto.MXCryptoError
+import org.matrix.android.sdk.api.session.crypto.model.OlmDecryptionResult
 import org.matrix.android.sdk.api.session.events.model.Event
 import org.matrix.android.sdk.api.session.events.model.EventType
 import org.matrix.android.sdk.api.session.events.model.toModel
@@ -30,18 +32,18 @@ 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.InitialSyncStrategy
+import org.matrix.android.sdk.api.session.sync.initialSyncStrategy
 import org.matrix.android.sdk.api.session.sync.model.InvitedRoomSync
 import org.matrix.android.sdk.api.session.sync.model.LazyRoomSyncEphemeral
 import org.matrix.android.sdk.api.session.sync.model.RoomSync
 import org.matrix.android.sdk.api.session.sync.model.RoomsSyncResponse
+import org.matrix.android.sdk.api.settings.LightweightSettingsStorage
 import org.matrix.android.sdk.internal.crypto.DefaultCryptoService
-import org.matrix.android.sdk.internal.crypto.MXCRYPTO_ALGORITHM_MEGOLM
-import org.matrix.android.sdk.internal.crypto.algorithms.olm.OlmDecryptionResult
 import org.matrix.android.sdk.internal.database.helper.addIfNecessary
 import org.matrix.android.sdk.internal.database.helper.addTimelineEvent
 import org.matrix.android.sdk.internal.database.helper.createOrUpdate
 import org.matrix.android.sdk.internal.database.helper.updateThreadSummaryIfNeeded
-import org.matrix.android.sdk.internal.database.lightweight.LightweightSettingsStorage
 import org.matrix.android.sdk.internal.database.mapper.asDomain
 import org.matrix.android.sdk.internal.database.mapper.toEntity
 import org.matrix.android.sdk.internal.database.model.ChunkEntity
@@ -74,9 +76,7 @@ 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.InitialSyncStrategy
 import org.matrix.android.sdk.internal.session.sync.SyncResponsePostTreatmentAggregator
-import org.matrix.android.sdk.internal.session.sync.initialSyncStrategy
 import org.matrix.android.sdk.internal.session.sync.parsing.RoomSyncAccountDataHandler
 import org.matrix.android.sdk.internal.util.computeBestChunkSize
 import timber.log.Timber
@@ -107,7 +107,6 @@ internal class RoomSyncHandler @Inject constructor(private val readReceiptHandle
                isInitialSync: Boolean,
                aggregator: SyncResponsePostTreatmentAggregator,
                reporter: ProgressReporter? = null) {
-        Timber.v("Execute transaction from $this")
         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)
@@ -207,6 +206,7 @@ internal class RoomSyncHandler @Inject constructor(private val readReceiptHandle
                                  syncLocalTimestampMillis: Long,
                                  aggregator: SyncResponsePostTreatmentAggregator): RoomEntity {
         Timber.v("Handle join sync for room $roomId")
+        val isInitialSync = insertType == EventInsertType.INITIAL_SYNC
 
         val ephemeralResult = (roomSync.ephemeral as? LazyRoomSyncEphemeral.Parsed)
                 ?._roomSyncEphemeral
@@ -241,7 +241,7 @@ internal class RoomSyncHandler @Inject constructor(private val readReceiptHandle
                 }
                 // Give info to crypto module
                 cryptoService.onStateEvent(roomId, event)
-                roomMemberEventHandler.handle(realm, roomId, event, aggregator)
+                roomMemberEventHandler.handle(realm, roomId, event, isInitialSync, aggregator)
             }
         }
         if (roomSync.timeline?.events?.isNotEmpty() == true) {
@@ -283,6 +283,7 @@ internal class RoomSyncHandler @Inject constructor(private val readReceiptHandle
                                   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)
         roomEntity.membership = Membership.INVITE
         if (roomSync.inviteState != null && roomSync.inviteState.events.isNotEmpty()) {
@@ -296,7 +297,7 @@ internal class RoomSyncHandler @Inject constructor(private val readReceiptHandle
                     eventId = eventEntity.eventId
                     root = eventEntity
                 }
-                roomMemberEventHandler.handle(realm, roomId, event)
+                roomMemberEventHandler.handle(realm, roomId, event, isInitialSync)
             }
         }
         val inviterEvent = roomSync.inviteState?.events?.lastOrNull {
@@ -312,6 +313,7 @@ internal class RoomSyncHandler @Inject constructor(private val readReceiptHandle
                                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) {
@@ -323,7 +325,7 @@ internal class RoomSyncHandler @Inject constructor(private val readReceiptHandle
                 eventId = event.eventId
                 root = eventEntity
             }
-            roomMemberEventHandler.handle(realm, roomId, event)
+            roomMemberEventHandler.handle(realm, roomId, event, isInitialSync)
         }
         for (event in roomSync.timeline?.events.orEmpty()) {
             if (event.eventId == null || event.senderId == null || event.type == null) {
@@ -337,7 +339,7 @@ internal class RoomSyncHandler @Inject constructor(private val readReceiptHandle
                     root = eventEntity
                 }
                 if (event.type == EventType.STATE_ROOM_MEMBER) {
-                    roomMemberEventHandler.handle(realm, roomEntity.roomId, event)
+                    roomMemberEventHandler.handle(realm, roomEntity.roomId, event, isInitialSync)
                 }
             }
         }
@@ -381,11 +383,11 @@ internal class RoomSyncHandler @Inject constructor(private val readReceiptHandle
                 continue
             }
 
-            eventIds.add(event.eventId)
-            liveEventService.get().dispatchLiveEventReceived(event, roomId, insertType == EventInsertType.INITIAL_SYNC)
-
             val isInitialSync = insertType == EventInsertType.INITIAL_SYNC
 
+            eventIds.add(event.eventId)
+            liveEventService.get().dispatchLiveEventReceived(event, roomId, isInitialSync)
+
             if (event.isEncrypted() && !isInitialSync) {
                 try {
                     decryptIfNeeded(event, roomId)
@@ -406,9 +408,8 @@ internal class RoomSyncHandler @Inject constructor(private val readReceiptHandle
                     root = eventEntity
                 }
                 if (event.type == EventType.STATE_ROOM_MEMBER) {
-                    val fixedContent = event.getFixedRoomMemberContent()
-                    roomMemberContentsByUser[event.stateKey] = fixedContent
-                    roomMemberEventHandler.handle(realm, roomEntity.roomId, event.stateKey, fixedContent, aggregator)
+                    roomMemberContentsByUser[event.stateKey] = event.getFixedRoomMemberContent()
+                    roomMemberEventHandler.handle(realm, roomEntity.roomId, event, isInitialSync, aggregator)
                 }
             }
 
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 db9799d51e..efc8e39a18 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
@@ -19,6 +19,7 @@ package org.matrix.android.sdk.internal.session.sync.handler.room
 import com.zhuinden.monarchy.Monarchy
 import io.realm.Realm
 import io.realm.kotlin.where
+import org.matrix.android.sdk.api.session.crypto.model.OlmDecryptionResult
 import org.matrix.android.sdk.api.session.events.model.Content
 import org.matrix.android.sdk.api.session.events.model.Event
 import org.matrix.android.sdk.api.session.events.model.EventType
@@ -35,9 +36,8 @@ import org.matrix.android.sdk.api.session.room.model.message.MessageType
 import org.matrix.android.sdk.api.session.room.model.relation.RelationDefaultContent
 import org.matrix.android.sdk.api.session.room.send.SendState
 import org.matrix.android.sdk.api.session.sync.model.SyncResponse
+import org.matrix.android.sdk.api.settings.LightweightSettingsStorage
 import org.matrix.android.sdk.api.util.JsonDict
-import org.matrix.android.sdk.internal.crypto.algorithms.olm.OlmDecryptionResult
-import org.matrix.android.sdk.internal.database.lightweight.LightweightSettingsStorage
 import org.matrix.android.sdk.internal.database.mapper.ContentMapper
 import org.matrix.android.sdk.internal.database.mapper.EventMapper
 import org.matrix.android.sdk.internal.database.mapper.asDomain
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 2460720adc..fc6a4e03d6 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
@@ -39,8 +39,8 @@ import org.matrix.android.sdk.api.session.sync.SyncState
 import org.matrix.android.sdk.api.session.sync.model.SyncResponse
 import org.matrix.android.sdk.internal.network.NetworkConnectivityChecker
 import org.matrix.android.sdk.internal.session.call.ActiveCallHandler
-import org.matrix.android.sdk.internal.session.sync.SyncPresence
 import org.matrix.android.sdk.internal.session.sync.SyncTask
+import org.matrix.android.sdk.internal.settings.DefaultLightweightSettingsStorage
 import org.matrix.android.sdk.internal.util.BackgroundDetectionObserver
 import org.matrix.android.sdk.internal.util.Debouncer
 import org.matrix.android.sdk.internal.util.createUIHandler
@@ -59,7 +59,8 @@ 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 activeCallHandler: ActiveCallHandler,
+                                              private val lightweightSettingsStorage: DefaultLightweightSettingsStorage
 ) : Thread("SyncThread"), NetworkConnectivityChecker.Listener, BackgroundDetectionObserver.Listener {
 
     private var state: SyncState = SyncState.Idle
@@ -104,10 +105,12 @@ internal class SyncThread @Inject constructor(private val syncTask: SyncTask,
 
     fun pause() = synchronized(lock) {
         if (isStarted) {
-            Timber.tag(loggerTag.value).d("Pause sync...")
+            Timber.tag(loggerTag.value).d("Pause sync... Not cancelling incremental sync")
             isStarted = false
             retryNoNetworkTask?.cancel()
-            syncScope.coroutineContext.cancelChildren()
+            // Do not cancel the current incremental sync.
+            // Incremental sync can be long and it requires the user to wait for the treatment to end,
+            // else all is restarted from the beginning each time the user moves the app to foreground.
         }
     }
 
@@ -180,7 +183,8 @@ internal class SyncThread @Inject constructor(private val syncTask: SyncTask,
                     else                            -> DEFAULT_LONG_POOL_TIMEOUT
                 }
                 Timber.tag(loggerTag.value).d("Execute sync request with timeout $timeout")
-                val params = SyncTask.Params(timeout, SyncPresence.Online, afterPause = afterPause)
+                val presence = lightweightSettingsStorage.getSyncPresenceStatus()
+                val params = SyncTask.Params(timeout, presence, afterPause = afterPause)
                 val sync = syncScope.launch {
                     previousSyncResponseHasToDevice = doSync(params)
                 }
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/job/SyncWorker.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/job/SyncWorker.kt
index c67c0e350e..f183c4cb28 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/job/SyncWorker.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/job/SyncWorker.kt
@@ -42,7 +42,7 @@ private const val DEFAULT_DELAY_MILLIS = 30_000L
  * Possible next worker    : None
  */
 internal class SyncWorker(context: Context, workerParameters: WorkerParameters, sessionManager: SessionManager) :
-    SessionSafeCoroutineWorker(context, workerParameters, sessionManager, Params::class.java) {
+        SessionSafeCoroutineWorker(context, workerParameters, sessionManager, Params::class.java) {
 
     @JsonClass(generateAdapter = true)
     internal data class Params(
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/parsing/DefaultLazyRoomSyncEphemeralJsonAdapter.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/parsing/DefaultLazyRoomSyncEphemeralJsonAdapter.kt
index 012470a076..c83f658bfe 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/parsing/DefaultLazyRoomSyncEphemeralJsonAdapter.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/parsing/DefaultLazyRoomSyncEphemeralJsonAdapter.kt
@@ -21,9 +21,9 @@ import com.squareup.moshi.JsonAdapter
 import com.squareup.moshi.JsonReader
 import com.squareup.moshi.JsonWriter
 import com.squareup.moshi.ToJson
+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.RoomSyncEphemeral
-import org.matrix.android.sdk.internal.session.sync.InitialSyncStrategy
 import org.matrix.android.sdk.internal.session.sync.RoomSyncEphemeralTemporaryStore
 import timber.log.Timber
 
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/parsing/InitialSyncResponseParser.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/parsing/InitialSyncResponseParser.kt
index f00cce2d5e..de3269ca1e 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/parsing/InitialSyncResponseParser.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/parsing/InitialSyncResponseParser.kt
@@ -19,8 +19,8 @@ package org.matrix.android.sdk.internal.session.sync.parsing
 import com.squareup.moshi.Moshi
 import okio.buffer
 import okio.source
+import org.matrix.android.sdk.api.session.sync.InitialSyncStrategy
 import org.matrix.android.sdk.api.session.sync.model.SyncResponse
-import org.matrix.android.sdk.internal.session.sync.InitialSyncStrategy
 import org.matrix.android.sdk.internal.session.sync.RoomSyncEphemeralTemporaryStore
 import timber.log.Timber
 import java.io.File
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 6205e3e4b1..5f62f40ab3 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
@@ -23,6 +23,7 @@ import org.matrix.android.sdk.api.failure.toRegistrationFlowResponse
 import org.matrix.android.sdk.api.session.accountdata.UserAccountDataTypes
 import org.matrix.android.sdk.api.session.events.model.toModel
 import org.matrix.android.sdk.api.session.terms.GetTermsResponse
+import org.matrix.android.sdk.api.session.terms.TermsResponse
 import org.matrix.android.sdk.api.session.terms.TermsService
 import org.matrix.android.sdk.api.util.JsonDict
 import org.matrix.android.sdk.internal.di.UnauthenticatedWithCertificate
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 fb6aff5a9e..1f117de67e 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
@@ -16,6 +16,7 @@
 
 package org.matrix.android.sdk.internal.session.terms
 
+import org.matrix.android.sdk.api.session.terms.TermsResponse
 import org.matrix.android.sdk.api.util.JsonDict
 import org.matrix.android.sdk.api.util.emptyJsonDict
 import org.matrix.android.sdk.internal.network.HttpHeaders
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/thirdparty/DefaultThirdPartyService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/thirdparty/DefaultThirdPartyService.kt
index fdd5524fc2..210cb192e7 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/thirdparty/DefaultThirdPartyService.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/thirdparty/DefaultThirdPartyService.kt
@@ -23,7 +23,7 @@ import javax.inject.Inject
 
 internal class DefaultThirdPartyService @Inject constructor(private val getThirdPartyProtocolTask: GetThirdPartyProtocolsTask,
                                                             private val getThirdPartyUserTask: GetThirdPartyUserTask) :
-    ThirdPartyService {
+        ThirdPartyService {
 
     override suspend fun getThirdPartyProtocols(): Map {
         return getThirdPartyProtocolTask.execute(Unit)
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/user/DefaultUserService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/user/DefaultUserService.kt
index 52b8cc3689..4ffc42e714 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
@@ -18,7 +18,6 @@ package org.matrix.android.sdk.internal.session.user
 
 import androidx.lifecycle.LiveData
 import androidx.paging.PagedList
-import org.matrix.android.sdk.api.session.profile.ProfileService
 import org.matrix.android.sdk.api.session.user.UserService
 import org.matrix.android.sdk.api.session.user.model.User
 import org.matrix.android.sdk.api.util.Optional
@@ -37,16 +36,10 @@ internal class DefaultUserService @Inject constructor(private val userDataSource
     }
 
     override suspend fun resolveUser(userId: String): User {
-        val known = getUser(userId)
-        if (known != null) {
-            return known
-        } else {
+        return getUser(userId) ?: run {
             val params = GetProfileInfoTask.Params(userId)
-            val data = getProfileInfoTask.execute(params)
-            return User(
-                    userId,
-                    data[ProfileService.DISPLAY_NAME_KEY] as? String,
-                    data[ProfileService.AVATAR_URL_KEY] as? String)
+            val json = getProfileInfoTask.execute(params)
+            User.fromJson(userId, json)
         }
     }
 
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/user/UserEntityFactory.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/user/UserEntityFactory.kt
index 9a9458e84b..46ea7547b0 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/user/UserEntityFactory.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/user/UserEntityFactory.kt
@@ -17,6 +17,7 @@
 package org.matrix.android.sdk.internal.session.user
 
 import org.matrix.android.sdk.api.session.room.model.RoomMemberContent
+import org.matrix.android.sdk.api.session.user.model.User
 import org.matrix.android.sdk.internal.database.model.UserEntity
 
 internal object UserEntityFactory {
@@ -24,8 +25,16 @@ internal object UserEntityFactory {
     fun create(userId: String, roomMember: RoomMemberContent): UserEntity {
         return UserEntity(
                 userId = userId,
-                displayName = roomMember.displayName ?: "",
-                avatarUrl = roomMember.avatarUrl ?: ""
+                displayName = roomMember.displayName.orEmpty(),
+                avatarUrl = roomMember.avatarUrl.orEmpty()
+        )
+    }
+
+    fun create(user: User): UserEntity {
+        return UserEntity(
+                userId = user.userId,
+                displayName = user.displayName.orEmpty(),
+                avatarUrl = user.avatarUrl.orEmpty()
         )
     }
 }
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/user/UserModule.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/user/UserModule.kt
index 4dfc7586ae..c205c4f1c6 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/user/UserModule.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/user/UserModule.kt
@@ -21,9 +21,7 @@ import dagger.Module
 import dagger.Provides
 import org.matrix.android.sdk.api.session.user.UserService
 import org.matrix.android.sdk.internal.session.SessionScope
-import org.matrix.android.sdk.internal.session.user.accountdata.DefaultSaveIgnoredUsersTask
 import org.matrix.android.sdk.internal.session.user.accountdata.DefaultUpdateIgnoredUserIdsTask
-import org.matrix.android.sdk.internal.session.user.accountdata.SaveIgnoredUsersTask
 import org.matrix.android.sdk.internal.session.user.accountdata.UpdateIgnoredUserIdsTask
 import org.matrix.android.sdk.internal.session.user.model.DefaultSearchUserTask
 import org.matrix.android.sdk.internal.session.user.model.SearchUserTask
@@ -48,9 +46,6 @@ internal abstract class UserModule {
     @Binds
     abstract fun bindSearchUserTask(task: DefaultSearchUserTask): SearchUserTask
 
-    @Binds
-    abstract fun bindSaveIgnoredUsersTask(task: DefaultSaveIgnoredUsersTask): SaveIgnoredUsersTask
-
     @Binds
     abstract fun bindUpdateIgnoredUserIdsTask(task: DefaultUpdateIgnoredUserIdsTask): UpdateIgnoredUserIdsTask
 
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/user/accountdata/AccountDataAPI.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/user/accountdata/AccountDataAPI.kt
index cc5625b255..bbeff18c01 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
@@ -21,7 +21,7 @@ import retrofit2.http.Body
 import retrofit2.http.PUT
 import retrofit2.http.Path
 
-interface AccountDataAPI {
+internal interface AccountDataAPI {
 
     /**
      * Set some account_data for the client.
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/user/accountdata/DefaultSessionAccountDataService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/user/accountdata/DefaultSessionAccountDataService.kt
index ddcac475ee..59c4dd671e 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/user/accountdata/DefaultSessionAccountDataService.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/user/accountdata/DefaultSessionAccountDataService.kt
@@ -23,12 +23,12 @@ import org.matrix.android.sdk.api.session.accountdata.UserAccountDataEvent
 import org.matrix.android.sdk.api.session.events.model.Content
 import org.matrix.android.sdk.api.session.room.accountdata.RoomAccountDataEvent
 import org.matrix.android.sdk.api.util.Optional
+import org.matrix.android.sdk.api.util.awaitCallback
 import org.matrix.android.sdk.internal.di.SessionDatabase
 import org.matrix.android.sdk.internal.session.room.accountdata.RoomAccountDataDataSource
 import org.matrix.android.sdk.internal.session.sync.handler.UserAccountDataSyncHandler
 import org.matrix.android.sdk.internal.task.TaskExecutor
 import org.matrix.android.sdk.internal.task.configureWith
-import org.matrix.android.sdk.internal.util.awaitCallback
 import javax.inject.Inject
 
 internal class DefaultSessionAccountDataService @Inject constructor(
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/user/accountdata/DirectChatsHelper.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/user/accountdata/DirectChatsHelper.kt
index c7b125b5d6..c4fbdc75ab 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/user/accountdata/DirectChatsHelper.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/user/accountdata/DirectChatsHelper.kt
@@ -24,8 +24,9 @@ import org.matrix.android.sdk.internal.di.SessionDatabase
 import org.matrix.android.sdk.internal.session.sync.model.accountdata.DirectMessagesContent
 import javax.inject.Inject
 
-internal class DirectChatsHelper @Inject constructor(@SessionDatabase
-                                                     private val realmConfiguration: RealmConfiguration) {
+internal class DirectChatsHelper @Inject constructor(
+        @SessionDatabase private val realmConfiguration: RealmConfiguration
+) {
 
     /**
      * @return a map of userId <-> list of roomId
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/user/accountdata/SaveIgnoredUsersTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/user/accountdata/SaveIgnoredUsersTask.kt
deleted file mode 100644
index 63c0ce645e..0000000000
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/user/accountdata/SaveIgnoredUsersTask.kt
+++ /dev/null
@@ -1,47 +0,0 @@
-/*
- * Copyright 2020 The Matrix.org Foundation C.I.C.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.matrix.android.sdk.internal.session.user.accountdata
-
-import com.zhuinden.monarchy.Monarchy
-import org.matrix.android.sdk.internal.database.model.IgnoredUserEntity
-import org.matrix.android.sdk.internal.di.SessionDatabase
-import org.matrix.android.sdk.internal.task.Task
-import org.matrix.android.sdk.internal.util.awaitTransaction
-import javax.inject.Inject
-
-/**
- * Save the ignored users list in DB
- */
-internal interface SaveIgnoredUsersTask : Task {
-    data class Params(
-            val userIds: List
-    )
-}
-
-internal class DefaultSaveIgnoredUsersTask @Inject constructor(@SessionDatabase private val monarchy: Monarchy) : SaveIgnoredUsersTask {
-
-    override suspend fun execute(params: SaveIgnoredUsersTask.Params) {
-        monarchy.awaitTransaction { realm ->
-            // clear current ignored users
-            realm.where(IgnoredUserEntity::class.java)
-                    .findAll()
-                    .deleteAllFromRealm()
-
-            // And save the new received list
-            params.userIds.forEach { realm.createObject(IgnoredUserEntity::class.java).apply { userId = it } }
-        }
-    }
-}
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/user/accountdata/UpdateIgnoredUserIdsTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/user/accountdata/UpdateIgnoredUserIdsTask.kt
index 445b78104c..173161f8ae 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/user/accountdata/UpdateIgnoredUserIdsTask.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/user/accountdata/UpdateIgnoredUserIdsTask.kt
@@ -38,7 +38,6 @@ internal interface UpdateIgnoredUserIdsTask : Task(JSON_DICT_PARAMETERIZED_TYPE)
 
@@ -88,10 +87,10 @@ internal class DefaultWidgetPostAPIMediator @Inject constructor(private val mosh
     }
 
     /*
-   * *********************************************************************************************
-   * Message sending methods
-   * *********************************************************************************************
-   */
+     * *********************************************************************************************
+     * Message sending methods
+     * *********************************************************************************************
+     */
 
     /**
      * Send a boolean response
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/widgets/DefaultWidgetService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/widgets/DefaultWidgetService.kt
index 89e827aea0..53a435d217 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/widgets/DefaultWidgetService.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/widgets/DefaultWidgetService.kt
@@ -29,7 +29,7 @@ import javax.inject.Provider
 internal class DefaultWidgetService @Inject constructor(private val widgetManager: WidgetManager,
                                                         private val widgetURLFormatter: WidgetURLFormatter,
                                                         private val widgetPostAPIMediator: Provider) :
-    WidgetService {
+        WidgetService {
 
     override fun getWidgetURLFormatter(): WidgetURLFormatter {
         return widgetURLFormatter
@@ -52,7 +52,7 @@ internal class DefaultWidgetService @Inject constructor(private val widgetManage
         return widgetManager.getWidgetComputedUrl(widget, isLightTheme)
     }
 
-override fun getRoomWidgetsLive(
+    override fun getRoomWidgetsLive(
             roomId: String,
             widgetId: QueryStringValue,
             widgetTypes: Set?,
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/widgets/WidgetManager.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/widgets/WidgetManager.kt
index 9f5f91d917..07ed91c179 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/widgets/WidgetManager.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/widgets/WidgetManager.kt
@@ -42,7 +42,6 @@ import org.matrix.android.sdk.internal.session.room.state.StateEventDataSource
 import org.matrix.android.sdk.internal.session.user.accountdata.UserAccountDataDataSource
 import org.matrix.android.sdk.internal.session.widgets.helper.WidgetFactory
 import org.matrix.android.sdk.internal.session.widgets.helper.extractWidgetSequence
-import java.util.HashMap
 import javax.inject.Inject
 
 @SessionScope
@@ -53,7 +52,7 @@ internal class WidgetManager @Inject constructor(private val integrationManager:
                                                  private val widgetFactory: WidgetFactory,
                                                  @UserId private val userId: String) :
 
-    IntegrationManagerService.Listener, SessionLifecycleObserver {
+        IntegrationManagerService.Listener, SessionLifecycleObserver {
 
     private val lifecycleOwner: LifecycleOwner = LifecycleOwner { lifecycleRegistry }
     private val lifecycleRegistry: LifecycleRegistry = LifecycleRegistry(lifecycleOwner)
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/settings/DefaultLightweightSettingsStorage.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/settings/DefaultLightweightSettingsStorage.kt
new file mode 100644
index 0000000000..284d5c9910
--- /dev/null
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/settings/DefaultLightweightSettingsStorage.kt
@@ -0,0 +1,74 @@
+/*
+ * 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.settings
+
+import android.content.Context
+import androidx.core.content.edit
+import androidx.preference.PreferenceManager
+import org.matrix.android.sdk.api.MatrixConfiguration
+import org.matrix.android.sdk.api.settings.LightweightSettingsStorage
+import org.matrix.android.sdk.internal.session.sync.SyncPresence
+import javax.inject.Inject
+
+/**
+ * The purpose of this class is to provide an alternative and lightweight way to store settings/data
+ * on the sdk without using the database. This should be used just for sdk/user preferences and
+ * not for large data sets
+ */
+internal class DefaultLightweightSettingsStorage @Inject constructor(
+        context: Context,
+        private val matrixConfiguration: MatrixConfiguration
+) : LightweightSettingsStorage {
+
+    private val sdkDefaultPrefs = PreferenceManager.getDefaultSharedPreferences(context.applicationContext)
+
+    override fun setThreadMessagesEnabled(enabled: Boolean) {
+        sdkDefaultPrefs.edit {
+            putBoolean(MATRIX_SDK_SETTINGS_THREAD_MESSAGES_ENABLED, enabled)
+        }
+    }
+
+    override fun areThreadMessagesEnabled(): Boolean {
+        return sdkDefaultPrefs.getBoolean(MATRIX_SDK_SETTINGS_THREAD_MESSAGES_ENABLED, matrixConfiguration.threadMessagesEnabledDefault)
+    }
+
+    /**
+     * Set the presence status sent on syncs when the application is in foreground.
+     *
+     * @param presence the presence status that should be sent on sync
+     */
+    internal fun setSyncPresenceStatus(presence: SyncPresence) {
+        sdkDefaultPrefs.edit {
+            putString(MATRIX_SDK_SETTINGS_FOREGROUND_PRESENCE_STATUS, presence.value)
+        }
+    }
+
+    /**
+     * Get the presence status that should be sent on syncs when the application is in foreground.
+     *
+     * @return the presence status that should be sent on sync
+     */
+    internal fun getSyncPresenceStatus(): SyncPresence {
+        val presenceString = sdkDefaultPrefs.getString(MATRIX_SDK_SETTINGS_FOREGROUND_PRESENCE_STATUS, SyncPresence.Online.value)
+        return SyncPresence.from(presenceString) ?: SyncPresence.Online
+    }
+
+    companion object {
+        const val MATRIX_SDK_SETTINGS_THREAD_MESSAGES_ENABLED = "MATRIX_SDK_SETTINGS_THREAD_MESSAGES_ENABLED"
+        private const val MATRIX_SDK_SETTINGS_FOREGROUND_PRESENCE_STATUS = "MATRIX_SDK_SETTINGS_FOREGROUND_PRESENCE_STATUS"
+    }
+}
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/settings/SettingsModule.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/settings/SettingsModule.kt
new file mode 100644
index 0000000000..db57f69596
--- /dev/null
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/settings/SettingsModule.kt
@@ -0,0 +1,28 @@
+/*
+ * Copyright 2022 The Matrix.org Foundation C.I.C.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+package org.matrix.android.sdk.internal.settings
+
+import dagger.Binds
+import dagger.Module
+import org.matrix.android.sdk.api.settings.LightweightSettingsStorage
+
+@Module
+internal abstract class SettingsModule {
+    @Binds
+    abstract fun bindLightweightSettingsStorage(storage: DefaultLightweightSettingsStorage): LightweightSettingsStorage
+}
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/util/BackgroundDetectionObserver.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/util/BackgroundDetectionObserver.kt
index 9c8b36a3ed..2dd16d8375 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/util/BackgroundDetectionObserver.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/util/BackgroundDetectionObserver.kt
@@ -20,7 +20,7 @@ import androidx.lifecycle.DefaultLifecycleObserver
 import androidx.lifecycle.LifecycleOwner
 import timber.log.Timber
 
-interface BackgroundDetectionObserver : DefaultLifecycleObserver {
+internal interface BackgroundDetectionObserver : DefaultLifecycleObserver {
     val isInBackground: Boolean
 
     fun register(listener: Listener)
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/util/CompatUtil.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/util/CompatUtil.kt
deleted file mode 100644
index 81d601f6f0..0000000000
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/util/CompatUtil.kt
+++ /dev/null
@@ -1,299 +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.
- */
-
-@file:Suppress("DEPRECATION")
-
-package org.matrix.android.sdk.internal.util
-
-import android.content.Context
-import android.content.SharedPreferences
-import android.os.Build
-import android.preference.PreferenceManager
-import android.security.KeyPairGeneratorSpec
-import android.security.keystore.KeyGenParameterSpec
-import android.security.keystore.KeyProperties
-import android.util.Base64
-import androidx.core.content.edit
-import timber.log.Timber
-import java.io.IOException
-import java.io.InputStream
-import java.io.OutputStream
-import java.math.BigInteger
-import java.security.InvalidAlgorithmParameterException
-import java.security.InvalidKeyException
-import java.security.KeyPairGenerator
-import java.security.KeyStore
-import java.security.KeyStoreException
-import java.security.NoSuchAlgorithmException
-import java.security.NoSuchProviderException
-import java.security.PrivateKey
-import java.security.SecureRandom
-import java.security.UnrecoverableKeyException
-import java.security.cert.CertificateException
-import java.security.spec.AlgorithmParameterSpec
-import java.security.spec.RSAKeyGenParameterSpec
-import java.util.Calendar
-import javax.crypto.Cipher
-import javax.crypto.CipherInputStream
-import javax.crypto.CipherOutputStream
-import javax.crypto.IllegalBlockSizeException
-import javax.crypto.KeyGenerator
-import javax.crypto.NoSuchPaddingException
-import javax.crypto.SecretKey
-import javax.crypto.spec.GCMParameterSpec
-import javax.crypto.spec.IvParameterSpec
-import javax.crypto.spec.SecretKeySpec
-import javax.security.auth.x500.X500Principal
-
-object CompatUtil {
-    private val TAG = CompatUtil::class.java.simpleName
-    private const val ANDROID_KEY_STORE_PROVIDER = "AndroidKeyStore"
-    private const val AES_GCM_CIPHER_TYPE = "AES/GCM/NoPadding"
-    private const val AES_GCM_KEY_SIZE_IN_BITS = 128
-    private const val AES_GCM_IV_LENGTH = 12
-    private const val AES_LOCAL_PROTECTION_KEY_ALIAS = "aes_local_protection"
-
-    private const val RSA_WRAP_LOCAL_PROTECTION_KEY_ALIAS = "rsa_wrap_local_protection"
-    private const val RSA_WRAP_CIPHER_TYPE = "RSA/NONE/PKCS1Padding"
-    private const val AES_WRAPPED_PROTECTION_KEY_SHARED_PREFERENCE = "aes_wrapped_local_protection"
-
-    private const val SHARED_KEY_ANDROID_VERSION_WHEN_KEY_HAS_BEEN_GENERATED = "android_version_when_key_has_been_generated"
-
-    private var sSecretKeyAndVersion: SecretKeyAndVersion? = null
-
-    /**
-     * Returns the unique SecureRandom instance shared for all local storage encryption operations.
-     */
-    private val prng: SecureRandom by lazy(LazyThreadSafetyMode.NONE) { SecureRandom() }
-
-    /**
-     * Returns the AES key used for local storage encryption/decryption with AES/GCM.
-     * The key is created if it does not exist already in the keystore.
-     * From Marshmallow, this key is generated and operated directly from the android keystore.
-     * From KitKat and before Marshmallow, this key is stored in the application shared preferences
-     * wrapped by a RSA key generated and operated directly from the android keystore.
-     *
-     * @param context the context holding the application shared preferences
-     */
-    @Synchronized
-    @Throws(KeyStoreException::class,
-            CertificateException::class,
-            NoSuchAlgorithmException::class,
-            IOException::class,
-            NoSuchProviderException::class,
-            InvalidAlgorithmParameterException::class,
-            NoSuchPaddingException::class,
-            InvalidKeyException::class,
-            IllegalBlockSizeException::class,
-            UnrecoverableKeyException::class)
-    private fun getAesGcmLocalProtectionKey(context: Context): SecretKeyAndVersion {
-        if (sSecretKeyAndVersion == null) {
-            val keyStore = KeyStore.getInstance(ANDROID_KEY_STORE_PROVIDER)
-            keyStore.load(null)
-
-            Timber.i(TAG, "Loading local protection key")
-
-            var key: SecretKey?
-
-            val sharedPreferences = PreferenceManager.getDefaultSharedPreferences(context)
-            // Get the version of Android when the key has been generated, default to the current version of the system. In this case, the
-            // key will be generated
-            val androidVersionWhenTheKeyHasBeenGenerated = sharedPreferences
-                    .getInt(SHARED_KEY_ANDROID_VERSION_WHEN_KEY_HAS_BEEN_GENERATED, Build.VERSION.SDK_INT)
-
-            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
-                if (keyStore.containsAlias(AES_LOCAL_PROTECTION_KEY_ALIAS)) {
-                    Timber.i(TAG, "AES local protection key found in keystore")
-                    key = keyStore.getKey(AES_LOCAL_PROTECTION_KEY_ALIAS, null) as SecretKey
-                } else {
-                    // Check if a key has been created on version < M (in case of OS upgrade)
-                    key = readKeyApiL(sharedPreferences, keyStore)
-
-                    if (key == null) {
-                        Timber.i(TAG, "Generating AES key with keystore")
-                        val generator = KeyGenerator.getInstance(KeyProperties.KEY_ALGORITHM_AES, ANDROID_KEY_STORE_PROVIDER)
-                        generator.init(
-                                KeyGenParameterSpec.Builder(AES_LOCAL_PROTECTION_KEY_ALIAS,
-                                        KeyProperties.PURPOSE_ENCRYPT or KeyProperties.PURPOSE_DECRYPT)
-                                        .setBlockModes(KeyProperties.BLOCK_MODE_GCM)
-                                        .setKeySize(AES_GCM_KEY_SIZE_IN_BITS)
-                                        .setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_NONE)
-                                        .build())
-                        key = generator.generateKey()
-
-                        sharedPreferences.edit {
-                            putInt(SHARED_KEY_ANDROID_VERSION_WHEN_KEY_HAS_BEEN_GENERATED, Build.VERSION.SDK_INT)
-                        }
-                    }
-                }
-            } else {
-                key = readKeyApiL(sharedPreferences, keyStore)
-
-                if (key == null) {
-                    Timber.i(TAG, "Generating RSA key pair with keystore")
-                    val generator = KeyPairGenerator.getInstance(KeyProperties.KEY_ALGORITHM_RSA, ANDROID_KEY_STORE_PROVIDER)
-                    val start = Calendar.getInstance()
-                    val end = Calendar.getInstance()
-                    end.add(Calendar.YEAR, 10)
-
-                    generator.initialize(
-                            KeyPairGeneratorSpec.Builder(context)
-                                    .setAlgorithmParameterSpec(RSAKeyGenParameterSpec(2048, RSAKeyGenParameterSpec.F4))
-                                    .setAlias(RSA_WRAP_LOCAL_PROTECTION_KEY_ALIAS)
-                                    .setSubject(X500Principal("CN=matrix-android-sdk"))
-                                    .setStartDate(start.time)
-                                    .setEndDate(end.time)
-                                    .setSerialNumber(BigInteger.ONE)
-                                    .build())
-                    val keyPair = generator.generateKeyPair()
-
-                    Timber.i(TAG, "Generating wrapped AES key")
-
-                    val aesKeyRaw = ByteArray(AES_GCM_KEY_SIZE_IN_BITS / java.lang.Byte.SIZE)
-                    prng.nextBytes(aesKeyRaw)
-                    key = SecretKeySpec(aesKeyRaw, "AES")
-
-                    val cipher = Cipher.getInstance(RSA_WRAP_CIPHER_TYPE)
-                    cipher.init(Cipher.WRAP_MODE, keyPair.public)
-                    val wrappedAesKey = cipher.wrap(key)
-
-                    sharedPreferences.edit {
-                        putString(AES_WRAPPED_PROTECTION_KEY_SHARED_PREFERENCE, Base64.encodeToString(wrappedAesKey, 0))
-                        putInt(SHARED_KEY_ANDROID_VERSION_WHEN_KEY_HAS_BEEN_GENERATED, Build.VERSION.SDK_INT)
-                    }
-                }
-            }
-
-            sSecretKeyAndVersion = SecretKeyAndVersion(key!!, androidVersionWhenTheKeyHasBeenGenerated)
-        }
-
-        return sSecretKeyAndVersion!!
-    }
-
-    /**
-     * Read the key, which may have been stored when the OS was < M
-     *
-     * @param sharedPreferences shared pref
-     * @param keyStore          key store
-     * @return the key if it exists or null
-     */
-    @Throws(KeyStoreException::class,
-            NoSuchPaddingException::class,
-            NoSuchAlgorithmException::class,
-            InvalidKeyException::class,
-            UnrecoverableKeyException::class)
-    private fun readKeyApiL(sharedPreferences: SharedPreferences, keyStore: KeyStore): SecretKey? {
-        val wrappedAesKeyString = sharedPreferences.getString(AES_WRAPPED_PROTECTION_KEY_SHARED_PREFERENCE, null)
-        if (wrappedAesKeyString != null && keyStore.containsAlias(RSA_WRAP_LOCAL_PROTECTION_KEY_ALIAS)) {
-            Timber.i(TAG, "RSA + wrapped AES local protection keys found in keystore")
-            val privateKey = keyStore.getKey(RSA_WRAP_LOCAL_PROTECTION_KEY_ALIAS, null) as PrivateKey
-            val wrappedAesKey = Base64.decode(wrappedAesKeyString, 0)
-            val cipher = Cipher.getInstance(RSA_WRAP_CIPHER_TYPE)
-            cipher.init(Cipher.UNWRAP_MODE, privateKey)
-            return cipher.unwrap(wrappedAesKey, "AES", Cipher.SECRET_KEY) as SecretKey
-        }
-
-        // Key does not exist
-        return null
-    }
-
-    /**
-     * Create a CipherOutputStream instance.
-     * Before Kitkat, this method will return out as local storage encryption is not implemented for
-     * devices before KitKat.
-     *
-     * @param out     the output stream
-     * @param context the context holding the application shared preferences
-     */
-    @Throws(IOException::class,
-            CertificateException::class,
-            NoSuchAlgorithmException::class,
-            UnrecoverableKeyException::class,
-            InvalidKeyException::class,
-            InvalidAlgorithmParameterException::class,
-            NoSuchPaddingException::class,
-            NoSuchProviderException::class,
-            KeyStoreException::class,
-            IllegalBlockSizeException::class)
-    fun createCipherOutputStream(out: OutputStream, context: Context): OutputStream? {
-        val keyAndVersion = getAesGcmLocalProtectionKey(context)
-
-        val cipher = Cipher.getInstance(AES_GCM_CIPHER_TYPE)
-        val iv: ByteArray
-
-        if (keyAndVersion.androidVersionWhenTheKeyHasBeenGenerated >= Build.VERSION_CODES.M) {
-            cipher.init(Cipher.ENCRYPT_MODE, keyAndVersion.secretKey)
-            iv = cipher.iv
-        } else {
-            iv = ByteArray(AES_GCM_IV_LENGTH)
-            prng.nextBytes(iv)
-            cipher.init(Cipher.ENCRYPT_MODE, keyAndVersion.secretKey, IvParameterSpec(iv))
-        }
-
-        if (iv.size != AES_GCM_IV_LENGTH) {
-            Timber.e(TAG, "Invalid IV length ${iv.size}")
-            return null
-        }
-
-        out.write(iv.size)
-        out.write(iv)
-
-        return CipherOutputStream(out, cipher)
-    }
-
-    /**
-     * Create a CipherInputStream instance.
-     * Warning, if inputStream is not an encrypted stream, it's up to the caller to close and reopen inputStream, because the stream has been read.
-     *
-     * @param inputStream the input stream
-     * @param context     the context holding the application shared preferences
-     * @return inputStream, or the created InputStream, or null if the InputStream inputStream does not contain encrypted data
-     */
-    @Throws(NoSuchPaddingException::class,
-            NoSuchAlgorithmException::class,
-            CertificateException::class,
-            InvalidKeyException::class,
-            KeyStoreException::class,
-            UnrecoverableKeyException::class,
-            IllegalBlockSizeException::class,
-            NoSuchProviderException::class,
-            InvalidAlgorithmParameterException::class,
-            IOException::class)
-    fun createCipherInputStream(inputStream: InputStream, context: Context): InputStream? {
-        val ivLen = inputStream.read()
-        if (ivLen != AES_GCM_IV_LENGTH) {
-            Timber.e(TAG, "Invalid IV length $ivLen")
-            return null
-        }
-
-        val iv = ByteArray(AES_GCM_IV_LENGTH)
-        inputStream.read(iv)
-
-        val cipher = Cipher.getInstance(AES_GCM_CIPHER_TYPE)
-
-        val keyAndVersion = getAesGcmLocalProtectionKey(context)
-
-        val spec: AlgorithmParameterSpec = if (keyAndVersion.androidVersionWhenTheKeyHasBeenGenerated >= Build.VERSION_CODES.M) {
-            GCMParameterSpec(AES_GCM_KEY_SIZE_IN_BITS, iv)
-        } else {
-            IvParameterSpec(iv)
-        }
-
-        cipher.init(Cipher.DECRYPT_MODE, keyAndVersion.secretKey, spec)
-
-        return CipherInputStream(inputStream, cipher)
-    }
-}
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/util/FileSaver.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/util/FileSaver.kt
index fb5e3a5774..3fcf35c127 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/util/FileSaver.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/util/FileSaver.kt
@@ -24,7 +24,7 @@ import java.io.InputStream
  * Save an input stream to a file with Okio
  */
 @WorkerThread
-fun writeToFile(inputStream: InputStream, outputFile: File) {
+internal fun writeToFile(inputStream: InputStream, outputFile: File) {
     // Ensure the parent folder exists, else it will crash
     outputFile.parentFile?.mkdirs()
 
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 a34b91a70b..5994cbcf93 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
@@ -28,7 +28,7 @@ import java.util.TreeSet
  * Build canonical Json
  * Doc: https://matrix.org/docs/spec/appendices.html#canonical-json
  */
-object JsonCanonicalizer {
+internal object JsonCanonicalizer {
 
     fun  getCanonicalJson(type: Class, o: T): String {
         val adapter = MoshiProvider.providesMoshi().adapter(type)
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/util/LiveDataUtils.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/util/LiveDataUtils.kt
deleted file mode 100644
index 80c3b83226..0000000000
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/util/LiveDataUtils.kt
+++ /dev/null
@@ -1,51 +0,0 @@
-/*
- * Copyright 2020 The Matrix.org Foundation C.I.C.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package org.matrix.android.sdk.internal.util
-
-import androidx.lifecycle.LiveData
-import androidx.lifecycle.MediatorLiveData
-
-object LiveDataUtils {
-
-    fun  combine(firstSource: LiveData,
-                                     secondSource: LiveData,
-                                     mapper: (FIRST, SECOND) -> OUT): LiveData {
-        return MediatorLiveData().apply {
-            var firstValue: FIRST? = null
-            var secondValue: SECOND? = null
-
-            val valueDispatcher = {
-                firstValue?.let { safeFirst ->
-                    secondValue?.let { safeSecond ->
-                        val mappedValue = mapper(safeFirst, safeSecond)
-                        postValue(mappedValue)
-                    }
-                }
-            }
-
-            addSource(firstSource) {
-                firstValue = it
-                valueDispatcher()
-            }
-
-            addSource(secondSource) {
-                secondValue = it
-                valueDispatcher()
-            }
-        }
-    }
-}
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/util/Monarchy.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/util/Monarchy.kt
index afe77d76d5..6152eacae5 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/util/Monarchy.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/util/Monarchy.kt
@@ -26,7 +26,7 @@ internal suspend fun  Monarchy.awaitTransaction(transaction: suspend (realm:
     return awaitTransaction(realmConfiguration, transaction)
 }
 
-fun  Monarchy.fetchCopied(query: (Realm) -> T?): T? {
+internal fun  Monarchy.fetchCopied(query: (Realm) -> T?): T? {
     val ref = AtomicReference()
     doWithRealm { realm ->
         val result = query.invoke(realm)?.let {
@@ -37,7 +37,7 @@ fun  Monarchy.fetchCopied(query: (Realm) -> T?): T? {
     return ref.get()
 }
 
-fun  Monarchy.fetchCopyMap(query: (Realm) -> T?, map: (T, realm: Realm) -> U): U? {
+internal fun  Monarchy.fetchCopyMap(query: (Realm) -> T?, map: (T, realm: Realm) -> U): U? {
     val ref = AtomicReference()
     doWithRealm { realm ->
         val result = query.invoke(realm)?.let {
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/util/Normalizer.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/util/Normalizer.kt
index 0e9c885394..0510521127 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/util/Normalizer.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/util/Normalizer.kt
@@ -19,7 +19,7 @@ package org.matrix.android.sdk.internal.util
 import java.text.Normalizer
 import javax.inject.Inject
 
-class Normalizer @Inject constructor() {
+internal class Normalizer @Inject constructor() {
 
     fun normalize(input: String): String {
         return Normalizer.normalize(input.lowercase(), Normalizer.Form.NFD)
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/util/TemporaryFileCreator.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/util/TemporaryFileCreator.kt
index 2790ffba36..dddd789212 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/util/TemporaryFileCreator.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/util/TemporaryFileCreator.kt
@@ -29,6 +29,7 @@ internal class TemporaryFileCreator @Inject constructor(
     suspend fun create(): File {
         return withContext(Dispatchers.IO) {
             File.createTempFile(UUID.randomUUID().toString(), null, context.cacheDir)
+                    .apply { mkdirs() }
         }
     }
 }
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/util/database/RealmMigrator.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/util/database/RealmMigrator.kt
index 15e82f3cc0..8f3c89f2d4 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/util/database/RealmMigrator.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/util/database/RealmMigrator.kt
@@ -20,8 +20,8 @@ import io.realm.DynamicRealm
 import io.realm.RealmObjectSchema
 import timber.log.Timber
 
-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/DefaultBuildVersionSdkIntProvider.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/util/system/DefaultBuildVersionSdkIntProvider.kt
index 2d5e4b944a..806c6e9735 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/util/system/DefaultBuildVersionSdkIntProvider.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/util/system/DefaultBuildVersionSdkIntProvider.kt
@@ -20,6 +20,6 @@ import android.os.Build
 import javax.inject.Inject
 
 internal class DefaultBuildVersionSdkIntProvider @Inject constructor() :
-    BuildVersionSdkIntProvider {
+        BuildVersionSdkIntProvider {
     override fun get() = Build.VERSION.SDK_INT
 }
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/worker/AlwaysSuccessfulWorker.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/worker/AlwaysSuccessfulWorker.kt
index 856d3debcf..c92b51fcb8 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/worker/AlwaysSuccessfulWorker.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/worker/AlwaysSuccessfulWorker.kt
@@ -20,7 +20,7 @@ import androidx.work.Worker
 import androidx.work.WorkerParameters
 
 internal class AlwaysSuccessfulWorker(context: Context, params: WorkerParameters) :
-    Worker(context, params) {
+        Worker(context, params) {
 
     override fun doWork(): Result {
         return Result.success()
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/worker/MatrixWorkerFactory.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/worker/MatrixWorkerFactory.kt
index 0b451e9c34..e56b359f7a 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/worker/MatrixWorkerFactory.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/worker/MatrixWorkerFactory.kt
@@ -93,7 +93,7 @@ internal class MatrixWorkerFactory @Inject constructor(private val sessionManage
     class CheckFactoryWorker(context: Context,
                              workerParameters: WorkerParameters,
                              private val isCreatedByMatrixWorkerFactory: Boolean) :
-        CoroutineWorker(context, workerParameters) {
+            CoroutineWorker(context, workerParameters) {
 
         // Called by WorkManager if there is no MatrixWorkerFactory
         constructor(context: Context, workerParameters: WorkerParameters) : this(context,
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/worker/SessionWorkerParams.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/worker/SessionWorkerParams.kt
index c6c038d2d2..de36b85660 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/worker/SessionWorkerParams.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/worker/SessionWorkerParams.kt
@@ -20,7 +20,7 @@ package org.matrix.android.sdk.internal.worker
  * Note about the Worker usage:
  * The workers we chain, or when using the append strategy, should never return Result.Failure(), else the chain will be broken forever
  */
-interface SessionWorkerParams {
+internal interface SessionWorkerParams {
     val sessionId: String
 
     /**
diff --git a/matrix-sdk-android/src/main/java/org/matrix/androidsdk/crypto/data/MXDeviceInfo.java b/matrix-sdk-android/src/main/java/org/matrix/androidsdk/crypto/data/MXDeviceInfo.java
index 1014ceda0e..4612b8d6ff 100755
--- a/matrix-sdk-android/src/main/java/org/matrix/androidsdk/crypto/data/MXDeviceInfo.java
+++ b/matrix-sdk-android/src/main/java/org/matrix/androidsdk/crypto/data/MXDeviceInfo.java
@@ -20,10 +20,9 @@ import java.io.Serializable;
 import java.util.List;
 import java.util.Map;
 
-/*
- * IMPORTANT: This class is imported from Riot-Android to be able to perform a migration. Do not use it for any other purpose
+/**
+ * IMPORTANT: This class is imported from Riot-Android to be able to perform a migration. Do not use it for any other purpose
  */
-
 public class MXDeviceInfo implements Serializable {
     private static final long serialVersionUID = 20129670646382964L;
 
diff --git a/matrix-sdk-android/src/main/java/org/matrix/androidsdk/crypto/data/MXOlmInboundGroupSession2.java b/matrix-sdk-android/src/main/java/org/matrix/androidsdk/crypto/data/MXOlmInboundGroupSession2.java
index 7277a86e9a..c6a8c1443c 100755
--- a/matrix-sdk-android/src/main/java/org/matrix/androidsdk/crypto/data/MXOlmInboundGroupSession2.java
+++ b/matrix-sdk-android/src/main/java/org/matrix/androidsdk/crypto/data/MXOlmInboundGroupSession2.java
@@ -23,11 +23,9 @@ import java.util.ArrayList;
 import java.util.List;
 import java.util.Map;
 
-/*
- * IMPORTANT: This class is imported from Riot-Android to be able to perform a migration. Do not use it for any other purpose
- */
-
 /**
+ * IMPORTANT: This class is imported from Riot-Android to be able to perform a migration. Do not use it for any other purpose
+ *
  * This class adds more context to a OLMInboundGroupSession object.
  * This allows additional checks. The class implements NSCoding so that the context can be stored.
  */
diff --git a/matrix-sdk-android/src/release/java/org/matrix/android/sdk/internal/network/interceptors/CurlLoggingInterceptor.kt b/matrix-sdk-android/src/release/java/org/matrix/android/sdk/internal/network/interceptors/CurlLoggingInterceptor.kt
index 47f6869479..0b7bac0d6f 100644
--- a/matrix-sdk-android/src/release/java/org/matrix/android/sdk/internal/network/interceptors/CurlLoggingInterceptor.kt
+++ b/matrix-sdk-android/src/release/java/org/matrix/android/sdk/internal/network/interceptors/CurlLoggingInterceptor.kt
@@ -27,7 +27,7 @@ import javax.inject.Inject
  */
 @MatrixScope
 internal class CurlLoggingInterceptor @Inject constructor() :
-    Interceptor {
+        Interceptor {
 
     @Throws(IOException::class)
     override fun intercept(chain: Interceptor.Chain): Response {
diff --git a/matrix-sdk-android/src/test/java/org/matrix/android/sdk/api/pushrules/PushRuleActionsTest.kt b/matrix-sdk-android/src/test/java/org/matrix/android/sdk/api/session/pushrules/PushRuleActionsTest.kt
similarity index 95%
rename from matrix-sdk-android/src/test/java/org/matrix/android/sdk/api/pushrules/PushRuleActionsTest.kt
rename to matrix-sdk-android/src/test/java/org/matrix/android/sdk/api/session/pushrules/PushRuleActionsTest.kt
index 9bfdea5414..1879e8195c 100644
--- a/matrix-sdk-android/src/test/java/org/matrix/android/sdk/api/pushrules/PushRuleActionsTest.kt
+++ b/matrix-sdk-android/src/test/java/org/matrix/android/sdk/api/session/pushrules/PushRuleActionsTest.kt
@@ -14,14 +14,14 @@
  * limitations under the License.
  */
 
-package org.matrix.android.sdk.api.pushrules
+package org.matrix.android.sdk.api.session.pushrules
 
 import org.junit.Assert.assertEquals
 import org.junit.Assert.assertNotNull
 import org.junit.Assert.assertTrue
 import org.junit.Test
 import org.matrix.android.sdk.MatrixTest
-import org.matrix.android.sdk.api.pushrules.rest.PushRule
+import org.matrix.android.sdk.api.session.pushrules.rest.PushRule
 import org.matrix.android.sdk.internal.di.MoshiProvider
 
 class PushRuleActionsTest : MatrixTest {
diff --git a/matrix-sdk-android/src/test/java/org/matrix/android/sdk/api/pushrules/PushRulesConditionTest.kt b/matrix-sdk-android/src/test/java/org/matrix/android/sdk/api/session/pushrules/PushRulesConditionTest.kt
similarity index 99%
rename from matrix-sdk-android/src/test/java/org/matrix/android/sdk/api/pushrules/PushRulesConditionTest.kt
rename to matrix-sdk-android/src/test/java/org/matrix/android/sdk/api/session/pushrules/PushRulesConditionTest.kt
index c0b869a90e..8b680449cd 100644
--- a/matrix-sdk-android/src/test/java/org/matrix/android/sdk/api/pushrules/PushRulesConditionTest.kt
+++ b/matrix-sdk-android/src/test/java/org/matrix/android/sdk/api/session/pushrules/PushRulesConditionTest.kt
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package org.matrix.android.sdk.api.pushrules
+package org.matrix.android.sdk.api.session.pushrules
 
 import io.mockk.every
 import io.mockk.mockk
diff --git a/matrix-sdk-android/src/test/java/org/matrix/android/sdk/internal/crypto/keysbackup/util/RecoveryKeyTest.kt b/matrix-sdk-android/src/test/java/org/matrix/android/sdk/internal/crypto/keysbackup/util/RecoveryKeyTest.kt
index 4e4548b197..d4c9da2986 100644
--- a/matrix-sdk-android/src/test/java/org/matrix/android/sdk/internal/crypto/keysbackup/util/RecoveryKeyTest.kt
+++ b/matrix-sdk-android/src/test/java/org/matrix/android/sdk/internal/crypto/keysbackup/util/RecoveryKeyTest.kt
@@ -22,6 +22,9 @@ import org.junit.Assert.assertFalse
 import org.junit.Assert.assertTrue
 import org.junit.Test
 import org.matrix.android.sdk.MatrixTest
+import org.matrix.android.sdk.api.session.crypto.keysbackup.computeRecoveryKey
+import org.matrix.android.sdk.api.session.crypto.keysbackup.extractCurveKeyFromRecoveryKey
+import org.matrix.android.sdk.api.session.crypto.keysbackup.isValidRecoveryKey
 
 class RecoveryKeyTest : MatrixTest {
 
diff --git a/matrix-sdk-android/src/test/java/org/matrix/android/sdk/internal/crypto/store/db/HelperTest.kt b/matrix-sdk-android/src/test/java/org/matrix/android/sdk/internal/crypto/store/db/HelperTest.kt
index b50d0581b0..39b3c5c731 100644
--- a/matrix-sdk-android/src/test/java/org/matrix/android/sdk/internal/crypto/store/db/HelperTest.kt
+++ b/matrix-sdk-android/src/test/java/org/matrix/android/sdk/internal/crypto/store/db/HelperTest.kt
@@ -19,7 +19,7 @@ package org.matrix.android.sdk.internal.crypto.store.db
 import org.junit.Assert.assertEquals
 import org.junit.Test
 import org.matrix.android.sdk.MatrixTest
-import org.matrix.android.sdk.internal.util.md5
+import org.matrix.android.sdk.api.util.md5
 
 class HelperTest : MatrixTest {
 
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 c8be0f5487..31fd86fe65 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
@@ -16,7 +16,8 @@
 
 package org.matrix.android.sdk.internal.session.pushers
 
-import kotlinx.coroutines.runBlocking
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.test.runTest
 import org.amshove.kluent.internal.assertFailsWith
 import org.amshove.kluent.shouldBeEqualTo
 import org.junit.Test
@@ -39,6 +40,7 @@ private val A_JSON_PUSHER = JsonPusher(
         data = JsonPusherData(brand = "Element")
 )
 
+@ExperimentalCoroutinesApi
 class DefaultAddPusherTaskTest {
 
     private val pushersAPI = FakePushersAPI()
@@ -55,7 +57,7 @@ class DefaultAddPusherTaskTest {
     fun `given no persisted pusher when adding Pusher then updates api and inserts result with Registered state`() {
         monarchy.givenWhereReturns(result = null)
 
-        runBlocking { addPusherTask.execute(AddPusherTask.Params(A_JSON_PUSHER)) }
+        runTest { addPusherTask.execute(AddPusherTask.Params(A_JSON_PUSHER)) }
 
         pushersAPI.verifySetPusher(A_JSON_PUSHER)
         monarchy.verifyInsertOrUpdate {
@@ -70,7 +72,7 @@ class DefaultAddPusherTaskTest {
         val realmResult = PusherEntity(appDisplayName = null)
         monarchy.givenWhereReturns(result = realmResult)
 
-        runBlocking { addPusherTask.execute(AddPusherTask.Params(A_JSON_PUSHER)) }
+        runTest { addPusherTask.execute(AddPusherTask.Params(A_JSON_PUSHER)) }
 
         pushersAPI.verifySetPusher(A_JSON_PUSHER)
 
@@ -85,7 +87,7 @@ class DefaultAddPusherTaskTest {
         pushersAPI.givenSetPusherErrors(SocketException())
 
         assertFailsWith {
-            runBlocking { addPusherTask.execute(AddPusherTask.Params(A_JSON_PUSHER)) }
+            runTest { addPusherTask.execute(AddPusherTask.Params(A_JSON_PUSHER)) }
         }
 
         realmResult.state shouldBeEqualTo PusherState.FAILED_TO_REGISTER
@@ -97,7 +99,7 @@ class DefaultAddPusherTaskTest {
         pushersAPI.givenSetPusherErrors(SocketException())
 
         assertFailsWith {
-            runBlocking { addPusherTask.execute(AddPusherTask.Params(A_JSON_PUSHER)) }
+            runTest { addPusherTask.execute(AddPusherTask.Params(A_JSON_PUSHER)) }
         }
     }
 }
diff --git a/matrix-sdk-android/src/test/java/org/matrix/android/sdk/internal/session/space/DefaultResolveSpaceInfoTaskTest.kt b/matrix-sdk-android/src/test/java/org/matrix/android/sdk/internal/session/space/DefaultResolveSpaceInfoTaskTest.kt
index f80c0f06d0..7203f89629 100644
--- a/matrix-sdk-android/src/test/java/org/matrix/android/sdk/internal/session/space/DefaultResolveSpaceInfoTaskTest.kt
+++ b/matrix-sdk-android/src/test/java/org/matrix/android/sdk/internal/session/space/DefaultResolveSpaceInfoTaskTest.kt
@@ -17,7 +17,7 @@
 package org.matrix.android.sdk.internal.session.space
 
 import kotlinx.coroutines.ExperimentalCoroutinesApi
-import kotlinx.coroutines.test.runBlockingTest
+import kotlinx.coroutines.test.runTest
 import okhttp3.ResponseBody.Companion.toResponseBody
 import org.amshove.kluent.shouldBeEqualTo
 import org.junit.Test
@@ -35,7 +35,7 @@ internal class DefaultResolveSpaceInfoTaskTest {
     private val resolveSpaceInfoTask = DefaultResolveSpaceInfoTask(spaceApi.instance, globalErrorReceiver)
 
     @Test
-    fun `given stable endpoint works, when execute, then return stable api data`() = runBlockingTest {
+    fun `given stable endpoint works, when execute, then return stable api data`() = runTest {
         spaceApi.givenStableEndpointReturns(response)
 
         val result = resolveSpaceInfoTask.execute(spaceApi.params)
@@ -44,7 +44,7 @@ internal class DefaultResolveSpaceInfoTaskTest {
     }
 
     @Test
-    fun `given stable endpoint fails, when execute, then fallback to unstable endpoint`() = runBlockingTest {
+    fun `given stable endpoint fails, when execute, then fallback to unstable endpoint`() = runTest {
         spaceApi.givenStableEndpointThrows(httpException)
         spaceApi.givenUnstableEndpointReturns(response)
 
diff --git a/matrix-sdk-android/src/test/java/org/matrix/android/sdk/internal/task/CoroutineSequencersTest.kt b/matrix-sdk-android/src/test/java/org/matrix/android/sdk/internal/task/CoroutineSequencersTest.kt
index 0abca8bee3..149b964fd2 100644
--- a/matrix-sdk-android/src/test/java/org/matrix/android/sdk/internal/task/CoroutineSequencersTest.kt
+++ b/matrix-sdk-android/src/test/java/org/matrix/android/sdk/internal/task/CoroutineSequencersTest.kt
@@ -21,7 +21,7 @@ import kotlinx.coroutines.asCoroutineDispatcher
 import kotlinx.coroutines.delay
 import kotlinx.coroutines.joinAll
 import kotlinx.coroutines.launch
-import kotlinx.coroutines.runBlocking
+import kotlinx.coroutines.test.runTest
 import org.junit.Assert.assertEquals
 import org.junit.Test
 import org.matrix.android.sdk.MatrixTest
@@ -51,7 +51,7 @@ class CoroutineSequencersTest : MatrixTest {
                             .also { results.add(it) }
                 }
         )
-        runBlocking {
+        runTest {
             jobs.joinAll()
         }
         assertEquals(3, results.size)
@@ -81,7 +81,7 @@ class CoroutineSequencersTest : MatrixTest {
                             .also { results.add(it) }
                 }
         )
-        runBlocking {
+        runTest {
             jobs.joinAll()
         }
         assertEquals(3, results.size)
@@ -109,7 +109,7 @@ class CoroutineSequencersTest : MatrixTest {
         )
         // We are canceling the second job
         jobs[1].cancel()
-        runBlocking {
+        runTest {
             jobs.joinAll()
         }
         assertEquals(2, results.size)
diff --git a/tools/check/check_code_quality.sh b/tools/check/check_code_quality.sh
index 9535ff9efb..e40d3635e8 100755
--- a/tools/check/check_code_quality.sh
+++ b/tools/check/check_code_quality.sh
@@ -66,7 +66,7 @@ echo "Search for forbidden patterns in code..."
 
 ${searchForbiddenStringsScript} ./tools/check/forbidden_strings_in_code.txt \
     ./matrix-sdk-android/src/main/java \
-    ./matrix-sdk-android-rx/src/main/java \
+    ./matrix-sdk-android-flow/src/main/java \
     ./vector/src/main/java \
     ./vector/src/debug/java \
     ./vector/src/release/java \
@@ -80,10 +80,22 @@ echo "Search for forbidden patterns specific for SDK code..."
 
 ${searchForbiddenStringsScript} ./tools/check/forbidden_strings_in_code_sdk.txt \
     ./matrix-sdk-android/src \
-    ./matrix-sdk-android-rx/src
+    ./matrix-sdk-android-flow/src
 
 resultForbiddenStringInCodeSdk=$?
 
+echo
+echo "Search for forbidden patterns specific for App code..."
+
+${searchForbiddenStringsScript} ./tools/check/forbidden_strings_in_code_app.txt \
+    ./vector/src/main/java \
+    ./vector/src/debug/java \
+    ./vector/src/release/java \
+    ./vector/src/fdroid/java \
+    ./vector/src/gplay/java
+
+resultForbiddenStringInCodeApp=$?
+
 echo
 echo "Search for forbidden patterns in resources..."
 
@@ -131,7 +143,7 @@ echo "Search for kotlin files with more than ${maxLines} lines..."
 
 ${checkLongFilesScript} ${maxLines} \
     ./matrix-sdk-android/src/main/java \
-    ./matrix-sdk-android-rx/src/main/java \
+    ./matrix-sdk-android-flow/src/main/java \
     ./vector/src/androidTest/java \
     ./vector/src/debug/java \
     ./vector/src/fdroid/java \
@@ -167,6 +179,7 @@ echo
 if [[ ${resultNbOfDrawable} -eq 0 ]] \
    && [[ ${resultForbiddenStringInCode} -eq 0 ]] \
    && [[ ${resultForbiddenStringInCodeSdk} -eq 0 ]] \
+   && [[ ${resultForbiddenStringInCodeApp} -eq 0 ]] \
    && [[ ${resultForbiddenStringInResource} -eq 0 ]] \
    && [[ ${resultForbiddenStringInLayout} -eq 0 ]] \
    && [[ ${resultLongFiles} -eq 0 ]] \
diff --git a/tools/check/forbidden_strings_in_code_app.txt b/tools/check/forbidden_strings_in_code_app.txt
new file mode 100644
index 0000000000..0715030e8f
--- /dev/null
+++ b/tools/check/forbidden_strings_in_code_app.txt
@@ -0,0 +1,18 @@
+#
+# Copyright 2022 New Vector Ltd
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+### You should not use code from the SDK package `org.matrix.android.sdk.internal`. Either move the code to the package `org.matrix.android.sdk.api`, or add a proper API to access this code, and add `internal` keyword SDK side.
+import org.matrix.android.sdk.internal
diff --git a/tools/ci/render_test_output.py b/tools/ci/render_test_output.py
index 1e7940ce04..f955b93cf9 100755
--- a/tools/ci/render_test_output.py
+++ b/tools/ci/render_test_output.py
@@ -13,32 +13,35 @@ print("::group::Arguments")
 print(f"{sys.argv}") 
 print("::endgroup::")
 for xmlfile in xmlfiles:
-    tree = ET.parse(xmlfile)
+    try:
+        tree = ET.parse(xmlfile)
     
-    root = tree.getroot()
-    name = root.attrib['name']
-    time = root.attrib['time']
-    tests = int(root.attrib['tests'])
-    skipped = int(root.attrib['skipped'])
-    errors = int(root.attrib['errors'])
-    failures = int(root.attrib['failures'])
-    success = tests - failures - errors - skipped
-    total = tests - skipped
-    print(f"::group::{name} {success}/{total} ({skipped} skipped) in {time}")
-    
-    for testcase in root:
-        if testcase.tag != "testcase":
-            continue
-        testname = testcase.attrib['classname']
-        message = testcase.attrib['name']
-        time = testcase.attrib['time']
-        child = testcase.find("failure")
-        if child is None:
-            print(f"{message} in {time}s")
-        else:
-            print(f"::error file={testname}::{message} in {time}s")
-            print(child.text)
-    body = f"passed={success} failures={failures} errors={errors} skipped={skipped}"
-    print(f"::set-output name={suitename}::={body}")
+        root = tree.getroot()
+        name = root.attrib['name']
+        time = root.attrib['time']
+        tests = int(root.attrib['tests'])
+        skipped = int(root.attrib['skipped'])
+        errors = int(root.attrib['errors'])
+        failures = int(root.attrib['failures'])
+        success = tests - failures - errors - skipped
+        total = tests - skipped
+        print(f"::group::{name} {success}/{total} ({skipped} skipped) in {time}")
+        
+        for testcase in root:
+            if testcase.tag != "testcase":
+                continue
+            testname = testcase.attrib['classname']
+            message = testcase.attrib['name']
+            time = testcase.attrib['time']
+            child = testcase.find("failure")
+            if child is None:
+                print(f"{message} in {time}s")
+            else:
+                print(f"::error file={testname}::{message} in {time}s")
+                print(child.text)
+        body = f" passed={success} failures={failures} errors={errors} skipped={skipped}"
+        print(f"::set-output name={suitename}::={body}")
+    except FileNotFoundError:
+        print(f"::error::Unable to open test results file {xmlfile} - check if the tests completed")
     print("::endgroup::")
 
diff --git a/tools/dependencycheck/suppressions.xml b/tools/dependencycheck/suppressions.xml
new file mode 100644
index 0000000000..758b1a87f3
--- /dev/null
+++ b/tools/dependencycheck/suppressions.xml
@@ -0,0 +1,17 @@
+
+
+    
+        
+        ^pkg:maven/com\.pinterest\.ktlint/ktlint\-reporter\-checkstyle@.*$
+        CVE-2019-10782
+    
+    
+        
+        ^pkg:maven/com\.pinterest\.ktlint/ktlint\-reporter\-checkstyle@.*$
+        CVE-2019-9658
+    
+
diff --git a/tools/emojis/emoji_picker_datasource_formatted.json b/tools/emojis/emoji_picker_datasource_formatted.json
index c1aa590003..a1b944a7eb 100644
--- a/tools/emojis/emoji_picker_datasource_formatted.json
+++ b/tools/emojis/emoji_picker_datasource_formatted.json
@@ -2056,7 +2056,9 @@
                 "disappear",
                 "dissolve",
                 "liquid",
-                "melt"
+                "melt",
+                "hot",
+                "heat"
             ]
         },
         "winking-face": {
@@ -2351,7 +2353,10 @@
                 "disbelief",
                 "embarrass",
                 "scared",
-                "surprise"
+                "surprise",
+                "silence",
+                "secret",
+                "shock"
             ]
         },
         "face-with-peeking-eye": {
@@ -2360,7 +2365,10 @@
             "j": [
                 "captivated",
                 "peep",
-                "stare"
+                "stare",
+                "scared",
+                "frightening",
+                "embarrassing"
             ]
         },
         "shushing-face": {
@@ -2392,7 +2400,8 @@
                 "salute",
                 "sunny",
                 "troops",
-                "yes"
+                "yes",
+                "respect"
             ]
         },
         "zippermouth-face": {
@@ -2467,7 +2476,10 @@
                 "disappear",
                 "hide",
                 "introvert",
-                "invisible"
+                "invisible",
+                "lonely",
+                "isolation",
+                "depression"
             ]
         },
         "face-in-clouds": {
@@ -2863,7 +2875,11 @@
                 "disappointed",
                 "meh",
                 "skeptical",
-                "unsure"
+                "unsure",
+                "skeptic",
+                "confuse",
+                "frustrated",
+                "indifferent"
             ]
         },
         "worried-face": {
@@ -2969,7 +2985,9 @@
                 "cry",
                 "proud",
                 "resist",
-                "sad"
+                "sad",
+                "touched",
+                "gratitude"
             ]
         },
         "frowning-face-with-open-mouth": {
@@ -4065,7 +4083,9 @@
             "j": [
                 "hand",
                 "right",
-                "rightward"
+                "rightward",
+                "palm",
+                "offer"
             ]
         },
         "leftwards-hand": {
@@ -4074,7 +4094,9 @@
             "j": [
                 "hand",
                 "left",
-                "leftward"
+                "leftward",
+                "palm",
+                "offer"
             ]
         },
         "palm-down-hand": {
@@ -4083,7 +4105,8 @@
             "j": [
                 "dismiss",
                 "drop",
-                "shoo"
+                "shoo",
+                "palm"
             ]
         },
         "palm-up-hand": {
@@ -4093,7 +4116,9 @@
                 "beckon",
                 "catch",
                 "come",
-                "offer"
+                "offer",
+                "lift",
+                "demand"
             ]
         },
         "ok-hand": {
@@ -4290,7 +4315,8 @@
             "b": "1FAF5",
             "j": [
                 "point",
-                "you"
+                "you",
+                "recruit"
             ]
         },
         "thumbs-up": {
@@ -4404,7 +4430,9 @@
             "a": "⊛ Heart Hands",
             "b": "1FAF6",
             "j": [
-                "love"
+                "love",
+                "appreciation",
+                "support"
             ]
         },
         "open-hands": {
@@ -4662,7 +4690,11 @@
                 "flirting",
                 "nervous",
                 "uncomfortable",
-                "worried"
+                "worried",
+                "flirt",
+                "sexy",
+                "pain",
+                "worry"
             ]
         },
         "baby": {
@@ -6058,7 +6090,8 @@
                 "monarch",
                 "noble",
                 "regal",
-                "royalty"
+                "royalty",
+                "power"
             ]
         },
         "prince": {
@@ -6231,7 +6264,8 @@
                 "belly",
                 "bloated",
                 "full",
-                "pregnant"
+                "pregnant",
+                "baby"
             ]
         },
         "pregnant-person": {
@@ -6241,7 +6275,8 @@
                 "belly",
                 "bloated",
                 "full",
-                "pregnant"
+                "pregnant",
+                "baby"
             ]
         },
         "breastfeeding": {
@@ -6635,7 +6670,8 @@
             "j": [
                 "fairy tale",
                 "fantasy",
-                "monster"
+                "monster",
+                "mystical"
             ]
         },
         "person-getting-massage": {
@@ -9374,7 +9410,8 @@
             "b": "1FAB8",
             "j": [
                 "ocean",
-                "reef"
+                "reef",
+                "sea"
             ]
         },
         "snail": {
@@ -9587,7 +9624,9 @@
                 "Hinduism",
                 "India",
                 "purity",
-                "Vietnam"
+                "Vietnam",
+                "calm",
+                "meditation"
             ]
         },
         "rosette": {
@@ -9832,14 +9871,16 @@
             "a": "⊛ Empty Nest",
             "b": "1FAB9",
             "j": [
-                "nesting"
+                "nesting",
+                "bird"
             ]
         },
         "nest-with-eggs": {
             "a": "⊛ Nest with Eggs",
             "b": "1FABA",
             "j": [
-                "nesting"
+                "nesting",
+                "bird"
             ]
         },
         "grapes": {
@@ -11187,7 +11228,9 @@
                 "drink",
                 "empty",
                 "glass",
-                "spill"
+                "spill",
+                "cup",
+                "water"
             ]
         },
         "cup-with-straw": {
@@ -12003,7 +12046,9 @@
             "b": "1F6DD",
             "j": [
                 "amusement park",
-                "play"
+                "play",
+                "fun",
+                "park"
             ]
         },
         "ferris-wheel": {
@@ -12533,7 +12578,9 @@
             "j": [
                 "circle",
                 "tire",
-                "turn"
+                "turn",
+                "car",
+                "transport"
             ]
         },
         "police-car-light": {
@@ -14666,7 +14713,8 @@
                 "hand",
                 "Mary",
                 "Miriam",
-                "protection"
+                "protection",
+                "religion"
             ]
         },
         "video-game": {
@@ -15864,7 +15912,9 @@
             "b": "1FAAB",
             "j": [
                 "electronic",
-                "low energy"
+                "low energy",
+                "drained",
+                "dead"
             ]
         },
         "electric-plug": {
@@ -17508,7 +17558,9 @@
                 "disability",
                 "hurt",
                 "mobility aid",
-                "stick"
+                "stick",
+                "accessibility",
+                "assist"
             ]
         },
         "stethoscope": {
@@ -17528,7 +17580,9 @@
                 "bones",
                 "doctor",
                 "medical",
-                "skeleton"
+                "skeleton",
+                "x-ray",
+                "medicine"
             ]
         },
         "door": {
@@ -17733,7 +17787,10 @@
                 "burp",
                 "clean",
                 "soap",
-                "underwater"
+                "underwater",
+                "fun",
+                "carbonation",
+                "sparkling"
             ]
         },
         "toothbrush": {
@@ -17856,7 +17913,8 @@
                 "credentials",
                 "ID",
                 "license",
-                "security"
+                "security",
+                "document"
             ]
         },
         "atm-sign": {
diff --git a/tools/hs_diag.py b/tools/hs_diag.py
index 7d7a947c4c..50f117bc8e 100755
--- a/tools/hs_diag.py
+++ b/tools/hs_diag.py
@@ -65,6 +65,6 @@ for item in items:
     print("# " + item[0] + " (" + item[1] + ")")
     print("====================================================================================================")
     if item[2]:
-        os.system("curl -s -X GET '" + item[1] + "' | python -m json.tool")
+        os.system("curl -s -X GET '" + item[1] + "' | python3 -m json.tool")
     else:
-        os.system("curl -s -X POST --data $'{}' '" + item[1] + "' | python -m json.tool")
+        os.system("curl -s -X POST --data $'{}' '" + item[1] + "' | python3 -m json.tool")
diff --git a/tools/jitsi/build_jisti_libs.sh b/tools/jitsi/build_jisti_libs.sh
index e352575775..445dc5e0fe 100755
--- a/tools/jitsi/build_jisti_libs.sh
+++ b/tools/jitsi/build_jisti_libs.sh
@@ -17,6 +17,9 @@ cd ..
 rm -rf jitsi-meet
 git clone https://github.com/jitsi/jitsi-meet
 
+# Android SDK
+export ANDROID_SDK_ROOT=~/Library/Android/sdk
+
 # We want a libre build!
 export LIBRE_BUILD=true
 
@@ -25,8 +28,9 @@ cd jitsi-meet
 # This is commit after version 2.2.2, which does not compile
 # git checkout 5a934c071a5cbe64de275a25d0ed62d8193cdd03
 
-# Version android-sdk-3.10.0, commit 99e56e229dfa3c490096e37c3e5b76d2a3f23e32
-git checkout android-sdk-3.10.0
+# Changelog: https://github.com/jitsi/jitsi-meet-release-notes/blob/master/CHANGELOG-MOBILE-SDKS.md
+
+git checkout android-sdk-5.0.2
 
 echo
 echo "##################################################"
diff --git a/tools/templates/ElementFeature/root/src/app_package/ViewModel.kt.ftl b/tools/templates/ElementFeature/root/src/app_package/ViewModel.kt.ftl
index 64e6a0f83f..62b1f40df5 100644
--- a/tools/templates/ElementFeature/root/src/app_package/ViewModel.kt.ftl
+++ b/tools/templates/ElementFeature/root/src/app_package/ViewModel.kt.ftl
@@ -7,7 +7,6 @@ import com.airbnb.mvrx.ViewModelContext
 import dagger.assisted.Assisted
 import dagger.assisted.AssistedInject
 import dagger.assisted.AssistedFactory
-import im.vector.app.core.extensions.exhaustive
 import im.vector.app.core.platform.VectorViewModel
 
 <#if createViewEvents>
@@ -42,6 +41,6 @@ class ${viewModelClass} @AssistedInject constructor(@Assisted initialState: ${vi
     override fun handle(action: ${actionClass}) {
         when (action) {
 
-        }.exhaustive
+        }
     }
 }
diff --git a/vector-config/src/main/res/values/config-features.xml b/vector-config/src/main/res/values/config-features.xml
new file mode 100755
index 0000000000..1a1a62446f
--- /dev/null
+++ b/vector-config/src/main/res/values/config-features.xml
@@ -0,0 +1,11 @@
+
+
+
+    
+
+    true
+
+
diff --git a/vector-config/src/main/res/values/config-settings.xml b/vector-config/src/main/res/values/config-settings.xml
index 0121ee9ae7..0a4da4c98e 100755
--- a/vector-config/src/main/res/values/config-settings.xml
+++ b/vector-config/src/main/res/values/config-settings.xml
@@ -13,7 +13,6 @@
     true
     true
     true
-    true
     true
     true
     true
@@ -28,16 +27,19 @@
 
     true
     false
+    false
+    false
 
     
 
-    
-
     
 
-    
+    true
 
-    
+    
+    false
+
+    
 
     
 
diff --git a/vector-config/src/main/res/values/urls.xml b/vector-config/src/main/res/values/urls.xml
new file mode 100644
index 0000000000..70f5227f5f
--- /dev/null
+++ b/vector-config/src/main/res/values/urls.xml
@@ -0,0 +1,7 @@
+
+
+    
+
+    https://element.io/help#threads
+    https://element.io/ems
+
\ No newline at end of file
diff --git a/vector/build.gradle b/vector/build.gradle
index aeaad19e02..162d9fe81c 100644
--- a/vector/build.gradle
+++ b/vector/build.gradle
@@ -18,7 +18,7 @@ ext.versionMinor = 4
 // Note: even values are reserved for regular release, odd values for hotfix release.
 // When creating a hotfix, you should decrease the value, since the current value
 // is the value for the next regular release.
-ext.versionPatch = 6
+ext.versionPatch = 14
 
 static def getGitTimestamp() {
     def cmd = 'git show -s --format=%ct'
@@ -151,7 +151,6 @@ android {
 
         buildConfigField "Boolean", "enableLocationSharing", "true"
         buildConfigField "String", "mapTilerKey", "\"fU3vlMsMn4Jb6dnEIFsx\""
-        buildConfigField "Boolean", "PRESENCE_SYNC_ENABLED", "true"
 
         testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
 
@@ -230,7 +229,6 @@ android {
             buildConfigField "boolean", "LOW_PRIVACY_LOG_ENABLE", "false"
             // Set to true if you want to enable strict mode in debug
             buildConfigField "boolean", "ENABLE_STRICT_MODE_LOGS", "false"
-            buildConfigField "Boolean", "ENABLE_LIVE_LOCATION_SHARING", "true"
 
             signingConfig signingConfigs.debug
         }
@@ -240,7 +238,6 @@ android {
 
             buildConfigField "boolean", "LOW_PRIVACY_LOG_ENABLE", "false"
             buildConfigField "boolean", "ENABLE_STRICT_MODE_LOGS", "false"
-            buildConfigField "Boolean", "ENABLE_LIVE_LOCATION_SHARING", "false"
 
             postprocessing {
                 removeUnusedCode true
@@ -371,7 +368,7 @@ dependencies {
     implementation 'com.facebook.stetho:stetho:1.6.0'
 
     // Phone number https://github.com/google/libphonenumber
-    implementation 'com.googlecode.libphonenumber:libphonenumber:8.12.45'
+    implementation 'com.googlecode.libphonenumber:libphonenumber:8.12.47'
 
     // FlowBinding
     implementation libs.github.flowBinding
@@ -411,7 +408,6 @@ dependencies {
     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'
-    implementation 'com.github.Armen101:AudioRecordView:1.0.5'
 
     // Custom Tab
     implementation 'androidx.browser:browser:1.4.0'
@@ -447,7 +443,8 @@ dependencies {
     implementation 'me.leolin:ShortcutBadger:1.1.22@aar'
 
     // Chat effects
-    implementation 'nl.dionsegijn:konfetti:1.3.2'
+    implementation 'nl.dionsegijn:konfetti-xml:2.0.2'
+
     implementation 'com.github.jetradarmobile:android-snowfall:1.2.1'
     // DI
     implementation libs.dagger.hilt
@@ -471,10 +468,10 @@ dependencies {
     // WebRTC
     // org.webrtc:google-webrtc is for development purposes only
     // implementation 'org.webrtc:google-webrtc:1.0.+'
-    implementation('com.facebook.react:react-native-webrtc:1.92.1-jitsi-9093212@aar')
+    implementation('com.facebook.react:react-native-webrtc:1.94.2-jitsi-10227332@aar')
 
     // Jitsi
-    implementation('org.jitsi.react:jitsi-meet-sdk:3.10.0') {
+    implementation('org.jitsi.react:jitsi-meet-sdk:5.0.2') {
         exclude group: 'com.google.firebase'
         exclude group: 'com.google.android.gms'
         exclude group: 'com.android.installreferrer'
diff --git a/vector/src/androidTest/java/im/vector/app/EspressoExt.kt b/vector/src/androidTest/java/im/vector/app/EspressoExt.kt
index 28564f7115..35554ae75a 100644
--- a/vector/src/androidTest/java/im/vector/app/EspressoExt.kt
+++ b/vector/src/androidTest/java/im/vector/app/EspressoExt.kt
@@ -48,9 +48,9 @@ import org.hamcrest.Matcher
 import org.hamcrest.Matchers
 import org.hamcrest.StringDescription
 import org.matrix.android.sdk.api.session.Session
+import org.matrix.android.sdk.api.session.crypto.crosssigning.PrivateKeysInfo
 import org.matrix.android.sdk.api.session.sync.SyncState
 import org.matrix.android.sdk.api.util.Optional
-import org.matrix.android.sdk.internal.crypto.store.PrivateKeysInfo
 import java.util.concurrent.TimeoutException
 
 object EspressoHelper {
@@ -198,7 +198,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/SecurityBootstrapTest.kt b/vector/src/androidTest/java/im/vector/app/SecurityBootstrapTest.kt
index 69fe63fb7b..a84aae9994 100644
--- a/vector/src/androidTest/java/im/vector/app/SecurityBootstrapTest.kt
+++ b/vector/src/androidTest/java/im/vector/app/SecurityBootstrapTest.kt
@@ -166,7 +166,7 @@ class SecurityBootstrapTest : VerificationTestBase() {
         assert(uiSession.cryptoService().crossSigningService().allPrivateKeysKnown())
         assert(uiSession.cryptoService().keysBackupService().isEnabled)
         assert(uiSession.cryptoService().keysBackupService().currentBackupVersion != null)
-        assert(uiSession.sharedSecretStorageService.isRecoverySetup())
-        assert(uiSession.sharedSecretStorageService.isMegolmKeyInBackup())
+        assert(uiSession.sharedSecretStorageService().isRecoverySetup())
+        assert(uiSession.sharedSecretStorageService().isMegolmKeyInBackup())
     }
 }
diff --git a/vector/src/androidTest/java/im/vector/app/ui/UiAllScreensSanityTest.kt b/vector/src/androidTest/java/im/vector/app/ui/UiAllScreensSanityTest.kt
index 5a03d5890a..4333558e7a 100644
--- a/vector/src/androidTest/java/im/vector/app/ui/UiAllScreensSanityTest.kt
+++ b/vector/src/androidTest/java/im/vector/app/ui/UiAllScreensSanityTest.kt
@@ -72,7 +72,6 @@ class UiAllScreensSanityTest {
             notifications { crawl() }
             preferences { crawl() }
             voiceAndVideo()
-            ignoredUsers()
             securityAndPrivacy { crawl() }
             labs()
             advancedSettings { crawl() }
diff --git a/vector/src/androidTest/java/im/vector/app/ui/robot/MessageMenuRobot.kt b/vector/src/androidTest/java/im/vector/app/ui/robot/MessageMenuRobot.kt
index 5c9ecfdef5..53ad2af7e6 100644
--- a/vector/src/androidTest/java/im/vector/app/ui/robot/MessageMenuRobot.kt
+++ b/vector/src/androidTest/java/im/vector/app/ui/robot/MessageMenuRobot.kt
@@ -75,6 +75,7 @@ class MessageMenuRobot(
         clickOn(R.string.reply_in_thread)
         autoClosed = true
     }
+
     fun viewInRoom() {
         clickOn(R.string.view_in_room)
         autoClosed = true
diff --git a/vector/src/androidTest/java/im/vector/app/ui/robot/OnboardingRobot.kt b/vector/src/androidTest/java/im/vector/app/ui/robot/OnboardingRobot.kt
index b3bb5172e8..97e3b281c0 100644
--- a/vector/src/androidTest/java/im/vector/app/ui/robot/OnboardingRobot.kt
+++ b/vector/src/androidTest/java/im/vector/app/ui/robot/OnboardingRobot.kt
@@ -21,6 +21,7 @@ import androidx.test.espresso.Espresso.onView
 import androidx.test.espresso.Espresso.pressBack
 import androidx.test.espresso.matcher.ViewMatchers.isRoot
 import androidx.test.espresso.matcher.ViewMatchers.withId
+import androidx.test.espresso.matcher.ViewMatchers.withText
 import com.adevinta.android.barista.assertion.BaristaEnabledAssertions.assertDisabled
 import com.adevinta.android.barista.assertion.BaristaEnabledAssertions.assertEnabled
 import com.adevinta.android.barista.assertion.BaristaVisibilityAssertions.assertDisplayed
@@ -28,6 +29,7 @@ import com.adevinta.android.barista.interaction.BaristaClickInteractions.clickOn
 import com.adevinta.android.barista.interaction.BaristaEditTextInteractions.writeTo
 import im.vector.app.R
 import im.vector.app.espresso.tools.waitUntilViewVisible
+import im.vector.app.features.DefaultVectorFeatures
 import im.vector.app.waitForView
 
 class OnboardingRobot {
@@ -55,6 +57,22 @@ class OnboardingRobot {
 
     fun createAccount(userId: String, password: String = "password", homeServerUrl: String = "http://10.0.2.2:8080") {
         initSession(true, userId, password, homeServerUrl)
+        waitUntilViewVisible(withText(R.string.ftue_account_created_congratulations_title))
+        if (DefaultVectorFeatures().isOnboardingPersonalizeEnabled()) {
+            clickOn(R.string.ftue_account_created_personalize)
+
+            waitUntilViewVisible(withText(R.string.ftue_display_name_title))
+            writeTo(R.id.displayNameInput, "UI automation")
+            clickOn(R.string.ftue_personalize_submit)
+
+            waitUntilViewVisible(withText(R.string.ftue_profile_picture_title))
+            clickOn(R.string.ftue_personalize_skip_this_step)
+
+            waitUntilViewVisible(withText(R.string.ftue_personalize_complete_title))
+            clickOn(R.string.ftue_personalize_lets_go)
+        } else {
+            clickOn(R.string.ftue_account_created_take_me_home)
+        }
     }
 
     fun login(userId: String, password: String = "password", homeServerUrl: String = "http://10.0.2.2:8080") {
diff --git a/vector/src/androidTest/java/im/vector/app/ui/robot/RoomDetailRobot.kt b/vector/src/androidTest/java/im/vector/app/ui/robot/RoomDetailRobot.kt
index 91409582d9..0ea1191fba 100644
--- a/vector/src/androidTest/java/im/vector/app/ui/robot/RoomDetailRobot.kt
+++ b/vector/src/androidTest/java/im/vector/app/ui/robot/RoomDetailRobot.kt
@@ -56,8 +56,10 @@ class RoomDetailRobot {
         // Menu
         openMenu()
         pressBack()
+        /* TODO something has changed in the menu :/
         clickMenu(R.id.voice_call)
         pressBack()
+         */
         clickMenu(R.id.video_call)
         pressBack()
     }
diff --git a/vector/src/androidTest/java/im/vector/app/ui/robot/settings/SettingsRobot.kt b/vector/src/androidTest/java/im/vector/app/ui/robot/settings/SettingsRobot.kt
index 97aee7ac4a..1697fe078d 100644
--- a/vector/src/androidTest/java/im/vector/app/ui/robot/settings/SettingsRobot.kt
+++ b/vector/src/androidTest/java/im/vector/app/ui/robot/settings/SettingsRobot.kt
@@ -44,15 +44,11 @@ class SettingsRobot {
         clickOnAndGoBack(R.string.preference_voice_and_video) { block() }
     }
 
-    fun ignoredUsers(block: () -> Unit = {}) {
-        clickOnAndGoBack(R.string.settings_ignored_users) { block() }
-    }
-
     fun securityAndPrivacy(block: SettingsSecurityRobot.() -> Unit) {
         clickOnAndGoBack(R.string.settings_security_and_privacy) { block(SettingsSecurityRobot()) }
     }
 
-    fun labs(shouldGoBack: Boolean = true, block:  () -> Unit = {}) {
+    fun labs(shouldGoBack: Boolean = true, block: () -> Unit = {}) {
         if (shouldGoBack) {
             clickOnAndGoBack(R.string.room_settings_labs_pref_title) { block() }
         } else {
diff --git a/vector/src/androidTest/java/im/vector/app/ui/robot/settings/SettingsSecurityRobot.kt b/vector/src/androidTest/java/im/vector/app/ui/robot/settings/SettingsSecurityRobot.kt
index 168db3e0e9..ef20d7764b 100644
--- a/vector/src/androidTest/java/im/vector/app/ui/robot/settings/SettingsSecurityRobot.kt
+++ b/vector/src/androidTest/java/im/vector/app/ui/robot/settings/SettingsSecurityRobot.kt
@@ -18,6 +18,7 @@ package im.vector.app.ui.robot.settings
 
 import androidx.test.espresso.Espresso
 import im.vector.app.R
+import im.vector.app.clickOnAndGoBack
 import im.vector.app.espresso.tools.clickOnPreference
 
 class SettingsSecurityRobot {
@@ -36,5 +37,11 @@ class SettingsSecurityRobot {
 
         clickOnPreference(R.string.settings_opt_in_of_analytics)
         Espresso.pressBack()
+
+        ignoredUsers()
+    }
+
+    private fun ignoredUsers(block: () -> Unit = {}) {
+        clickOnAndGoBack(R.string.settings_ignored_users) { block() }
     }
 }
diff --git a/vector/src/androidTest/java/im/vector/app/ui/robot/space/SpaceRobot.kt b/vector/src/androidTest/java/im/vector/app/ui/robot/space/SpaceRobot.kt
index ffb3c24051..bfcf312e76 100644
--- a/vector/src/androidTest/java/im/vector/app/ui/robot/space/SpaceRobot.kt
+++ b/vector/src/androidTest/java/im/vector/app/ui/robot/space/SpaceRobot.kt
@@ -24,7 +24,7 @@ class SpaceRobot {
 
     fun createSpace(block: SpaceCreateRobot.() -> Unit) {
         openDrawer()
-        clickOn(R.string.add_space)
+        clickOn(R.string.create_space)
         block(SpaceCreateRobot())
     }
 
diff --git a/vector/src/debug/AndroidManifest.xml b/vector/src/debug/AndroidManifest.xml
index 0b2b5cf90f..87aade0c8b 100644
--- a/vector/src/debug/AndroidManifest.xml
+++ b/vector/src/debug/AndroidManifest.xml
@@ -2,6 +2,8 @@
 
 
+    
+
     
         
         
diff --git a/vector/src/debug/java/im/vector/app/config/AnalyticsConfig.kt b/vector/src/debug/java/im/vector/app/config/AnalyticsConfig.kt
index 34f2d4f92b..63f14f72f6 100644
--- a/vector/src/debug/java/im/vector/app/config/AnalyticsConfig.kt
+++ b/vector/src/debug/java/im/vector/app/config/AnalyticsConfig.kt
@@ -21,7 +21,7 @@ import im.vector.app.features.analytics.AnalyticsConfig
 
 val analyticsConfig: AnalyticsConfig = object : AnalyticsConfig {
     override val isEnabled = BuildConfig.APPLICATION_ID == "im.vector.app.debug"
-    override val postHogHost = "https://posthog-poc.lab.element.dev"
-    override val postHogApiKey = "rs-pJjsYJTuAkXJfhaMmPUNBhWliDyTKLOOxike6ck8"
+    override val postHogHost = "https://posthog.element.dev"
+    override val postHogApiKey = "phc_VtA1L35nw3aeAtHIx1ayrGdzGkss7k1xINeXcoIQzXN"
     override val policyLink = "https://element.io/cookie-policy"
 }
diff --git a/vector/src/debug/java/im/vector/app/features/debug/DebugMenuActivity.kt b/vector/src/debug/java/im/vector/app/features/debug/DebugMenuActivity.kt
index a2b2b44ce3..cc69cfd426 100644
--- a/vector/src/debug/java/im/vector/app/features/debug/DebugMenuActivity.kt
+++ b/vector/src/debug/java/im/vector/app/features/debug/DebugMenuActivity.kt
@@ -49,7 +49,6 @@ import im.vector.lib.ui.styles.debug.DebugVectorButtonStylesDarkActivity
 import im.vector.lib.ui.styles.debug.DebugVectorButtonStylesLightActivity
 import im.vector.lib.ui.styles.debug.DebugVectorTextViewDarkActivity
 import im.vector.lib.ui.styles.debug.DebugVectorTextViewLightActivity
-import org.matrix.android.sdk.internal.crypto.verification.qrcode.toQrCodeData
 import timber.log.Timber
 import javax.inject.Inject
 
@@ -251,7 +250,7 @@ class DebugMenuActivity : VectorBaseActivity() {
             // renderQrCode(QrCodeScannerActivity.getResultText(data) ?: "")
             val result = QrCodeScannerActivity.getResultText(activityResult.data)!!
 
-            val qrCodeData = result.toQrCodeData()
+            val qrCodeData = null // This is now internal: result.toQrCodeData()
             Timber.e("qrCodeData: $qrCodeData")
 
             if (result.length != buffer.size) {
@@ -265,6 +264,8 @@ class DebugMenuActivity : VectorBaseActivity() {
                     }
                 }
             }
+            // Ensure developer will see that this cannot work anymore
+            error("toQrCodeData() is now internal")
         }
     }
 }
diff --git a/vector/src/debug/java/im/vector/app/features/debug/TestLinkifyActivity.kt b/vector/src/debug/java/im/vector/app/features/debug/TestLinkifyActivity.kt
index 88e55d6760..59c60e0e15 100644
--- a/vector/src/debug/java/im/vector/app/features/debug/TestLinkifyActivity.kt
+++ b/vector/src/debug/java/im/vector/app/features/debug/TestLinkifyActivity.kt
@@ -93,7 +93,7 @@ class TestLinkifyActivity : AppCompatActivity() {
                                         .show()
                             }
                         })
-                        */
+                         */
                     }
 
                     subViews.testLinkifyCustomText.apply {
@@ -108,7 +108,7 @@ class TestLinkifyActivity : AppCompatActivity() {
                                         .show()
                             }
                         })
-                        */
+                         */
 
                         // TODO Call VectorLinkify.addLinks(text)
                     }
diff --git a/vector/src/debug/java/im/vector/app/features/debug/analytics/DebugAnalyticsViewModel.kt b/vector/src/debug/java/im/vector/app/features/debug/analytics/DebugAnalyticsViewModel.kt
index 03e416813a..e007e61c1c 100644
--- a/vector/src/debug/java/im/vector/app/features/debug/analytics/DebugAnalyticsViewModel.kt
+++ b/vector/src/debug/java/im/vector/app/features/debug/analytics/DebugAnalyticsViewModel.kt
@@ -22,7 +22,6 @@ import dagger.assisted.AssistedFactory
 import dagger.assisted.AssistedInject
 import im.vector.app.core.di.MavericksAssistedViewModelFactory
 import im.vector.app.core.di.hiltMavericksViewModelFactory
-import im.vector.app.core.extensions.exhaustive
 import im.vector.app.core.platform.EmptyViewEvents
 import im.vector.app.core.platform.VectorViewModel
 import im.vector.app.features.analytics.store.AnalyticsStore
@@ -53,7 +52,7 @@ class DebugAnalyticsViewModel @AssistedInject constructor(
     override fun handle(action: DebugAnalyticsViewActions) {
         when (action) {
             DebugAnalyticsViewActions.ResetAnalyticsOptInDisplayed -> handleResetAnalyticsOptInDisplayed()
-        }.exhaustive
+        }
     }
 
     private fun handleResetAnalyticsOptInDisplayed() {
diff --git a/vector/src/debug/java/im/vector/app/features/debug/features/DebugFeaturesStateFactory.kt b/vector/src/debug/java/im/vector/app/features/debug/features/DebugFeaturesStateFactory.kt
index 8702c8d966..5b3ee30722 100644
--- a/vector/src/debug/java/im/vector/app/features/debug/features/DebugFeaturesStateFactory.kt
+++ b/vector/src/debug/java/im/vector/app/features/debug/features/DebugFeaturesStateFactory.kt
@@ -54,6 +54,16 @@ class DebugFeaturesStateFactory @Inject constructor(
                         key = DebugFeatureKeys.onboardingPersonalize,
                         factory = VectorFeatures::isOnboardingPersonalizeEnabled
                 ),
+                createBooleanFeature(
+                        label = "FTUE Combined register",
+                        key = DebugFeatureKeys.onboardingCombinedRegister,
+                        factory = VectorFeatures::isOnboardingCombinedRegisterEnabled
+                ),
+                createBooleanFeature(
+                        label = "Live location sharing",
+                        key = DebugFeatureKeys.liveLocationSharing,
+                        factory = VectorFeatures::isLiveLocationEnabled
+                ),
         ))
     }
 
diff --git a/vector/src/debug/java/im/vector/app/features/debug/features/DebugVectorFeatures.kt b/vector/src/debug/java/im/vector/app/features/debug/features/DebugVectorFeatures.kt
index f93e3d96fb..07fab8a58d 100644
--- a/vector/src/debug/java/im/vector/app/features/debug/features/DebugVectorFeatures.kt
+++ b/vector/src/debug/java/im/vector/app/features/debug/features/DebugVectorFeatures.kt
@@ -54,6 +54,15 @@ class DebugVectorFeatures(
     override fun isOnboardingPersonalizeEnabled(): Boolean = read(DebugFeatureKeys.onboardingPersonalize)
             ?: vectorFeatures.isOnboardingPersonalizeEnabled()
 
+    override fun isOnboardingCombinedRegisterEnabled(): Boolean = read(DebugFeatureKeys.onboardingCombinedRegister)
+            ?: vectorFeatures.isOnboardingCombinedRegisterEnabled()
+
+    override fun isLiveLocationEnabled(): Boolean = read(DebugFeatureKeys.liveLocationSharing)
+            ?: vectorFeatures.isLiveLocationEnabled()
+
+    override fun isScreenSharingEnabled(): Boolean = read(DebugFeatureKeys.screenSharing)
+            ?: vectorFeatures.isScreenSharingEnabled()
+
     fun  override(value: T?, key: Preferences.Key) = updatePreferences {
         if (value == null) {
             it.remove(key)
@@ -104,6 +113,9 @@ private fun > enumPreferencesKey(type: KClass) = stringPreference
 object DebugFeatureKeys {
     val onboardingAlreadyHaveAnAccount = booleanPreferencesKey("onboarding-already-have-an-account")
     val onboardingSplashCarousel = booleanPreferencesKey("onboarding-splash-carousel")
-    val onboardingUseCase = booleanPreferencesKey("onbboarding-splash-carousel")
-    val onboardingPersonalize = booleanPreferencesKey("onbboarding-personalize")
+    val onboardingUseCase = booleanPreferencesKey("onboarding-splash-carousel")
+    val onboardingPersonalize = booleanPreferencesKey("onboarding-personalize")
+    val onboardingCombinedRegister = booleanPreferencesKey("onboarding-combined-register")
+    val liveLocationSharing = booleanPreferencesKey("live-location-sharing")
+    val screenSharing = booleanPreferencesKey("screen-sharing")
 }
diff --git a/vector/src/debug/java/im/vector/app/features/debug/sas/SasEmojiItem.kt b/vector/src/debug/java/im/vector/app/features/debug/sas/SasEmojiItem.kt
index a7b74f3b59..9e0c013960 100644
--- a/vector/src/debug/java/im/vector/app/features/debug/sas/SasEmojiItem.kt
+++ b/vector/src/debug/java/im/vector/app/features/debug/sas/SasEmojiItem.kt
@@ -32,6 +32,7 @@ abstract class SasEmojiItem : VectorEpoxyModel() {
 
     @EpoxyAttribute
     var index: Int = 0
+
     @EpoxyAttribute
     lateinit var emojiRepresentation: EmojiRepresentation
 
diff --git a/vector/src/debug/java/im/vector/app/features/debug/settings/DebugPrivateSettingsViewModel.kt b/vector/src/debug/java/im/vector/app/features/debug/settings/DebugPrivateSettingsViewModel.kt
index 62871023bc..1d77d031af 100644
--- a/vector/src/debug/java/im/vector/app/features/debug/settings/DebugPrivateSettingsViewModel.kt
+++ b/vector/src/debug/java/im/vector/app/features/debug/settings/DebugPrivateSettingsViewModel.kt
@@ -22,7 +22,6 @@ import dagger.assisted.AssistedFactory
 import dagger.assisted.AssistedInject
 import im.vector.app.core.di.MavericksAssistedViewModelFactory
 import im.vector.app.core.di.hiltMavericksViewModelFactory
-import im.vector.app.core.extensions.exhaustive
 import im.vector.app.core.platform.EmptyViewEvents
 import im.vector.app.core.platform.VectorViewModel
 import im.vector.app.features.debug.features.DebugVectorOverrides
@@ -69,9 +68,9 @@ class DebugPrivateSettingsViewModel @AssistedInject constructor(
         when (action) {
             is DebugPrivateSettingsViewActions.SetDialPadVisibility         -> handleSetDialPadVisibility(action)
             is DebugPrivateSettingsViewActions.SetForceLoginFallbackEnabled -> handleSetForceLoginFallbackEnabled(action)
-            is SetDisplayNameCapabilityOverride                             -> handSetDisplayNameCapabilityOverride(action)
-            is SetAvatarCapabilityOverride                                  -> handSetAvatarCapabilityOverride(action)
-        }.exhaustive
+            is SetDisplayNameCapabilityOverride                             -> handleSetDisplayNameCapabilityOverride(action)
+            is SetAvatarCapabilityOverride                                  -> handleSetAvatarCapabilityOverride(action)
+        }
     }
 
     private fun handleSetDialPadVisibility(action: DebugPrivateSettingsViewActions.SetDialPadVisibility) {
@@ -86,14 +85,14 @@ class DebugPrivateSettingsViewModel @AssistedInject constructor(
         }
     }
 
-    private fun handSetDisplayNameCapabilityOverride(action: SetDisplayNameCapabilityOverride) {
+    private fun handleSetDisplayNameCapabilityOverride(action: SetDisplayNameCapabilityOverride) {
         viewModelScope.launch {
             val forceDisplayName = action.option.toBoolean()
             debugVectorOverrides.setHomeserverCapabilities { copy(canChangeDisplayName = forceDisplayName) }
         }
     }
 
-    private fun handSetAvatarCapabilityOverride(action: SetAvatarCapabilityOverride) {
+    private fun handleSetAvatarCapabilityOverride(action: SetAvatarCapabilityOverride) {
         viewModelScope.launch {
             val forceAvatar = action.option.toBoolean()
             debugVectorOverrides.setHomeserverCapabilities { copy(canChangeAvatar = forceAvatar) }
diff --git a/vector/src/debug/java/im/vector/app/features/debug/settings/OverrideDropdownView.kt b/vector/src/debug/java/im/vector/app/features/debug/settings/OverrideDropdownView.kt
index 48ec44f909..b8f89eaf88 100644
--- a/vector/src/debug/java/im/vector/app/features/debug/settings/OverrideDropdownView.kt
+++ b/vector/src/debug/java/im/vector/app/features/debug/settings/OverrideDropdownView.kt
@@ -24,7 +24,6 @@ import android.view.View
 import android.widget.AdapterView
 import android.widget.ArrayAdapter
 import android.widget.LinearLayout
-import im.vector.app.R
 import im.vector.app.databinding.ViewBooleanDropdownBinding
 
 class OverrideDropdownView @JvmOverloads constructor(
diff --git a/vector/src/fdroid/java/im/vector/app/fdroid/features/settings/troubleshoot/TestAutoStartBoot.kt b/vector/src/fdroid/java/im/vector/app/fdroid/features/settings/troubleshoot/TestAutoStartBoot.kt
index 27a3f09ddc..6d741a36ba 100644
--- a/vector/src/fdroid/java/im/vector/app/fdroid/features/settings/troubleshoot/TestAutoStartBoot.kt
+++ b/vector/src/fdroid/java/im/vector/app/fdroid/features/settings/troubleshoot/TestAutoStartBoot.kt
@@ -28,7 +28,7 @@ import javax.inject.Inject
  */
 class TestAutoStartBoot @Inject constructor(private val vectorPreferences: VectorPreferences,
                                             private val stringProvider: StringProvider) :
-    TroubleshootTest(R.string.settings_troubleshoot_test_service_boot_title) {
+        TroubleshootTest(R.string.settings_troubleshoot_test_service_boot_title) {
 
     override fun perform(activityResultLauncher: ActivityResultLauncher) {
         if (vectorPreferences.autoStartOnBoot()) {
diff --git a/vector/src/fdroid/java/im/vector/app/fdroid/features/settings/troubleshoot/TestBackgroundRestrictions.kt b/vector/src/fdroid/java/im/vector/app/fdroid/features/settings/troubleshoot/TestBackgroundRestrictions.kt
index b5635e186c..f8c30f813d 100644
--- a/vector/src/fdroid/java/im/vector/app/fdroid/features/settings/troubleshoot/TestBackgroundRestrictions.kt
+++ b/vector/src/fdroid/java/im/vector/app/fdroid/features/settings/troubleshoot/TestBackgroundRestrictions.kt
@@ -28,7 +28,7 @@ import javax.inject.Inject
 
 class TestBackgroundRestrictions @Inject constructor(private val context: FragmentActivity,
                                                      private val stringProvider: StringProvider) :
-    TroubleshootTest(R.string.settings_troubleshoot_test_bg_restricted_title) {
+        TroubleshootTest(R.string.settings_troubleshoot_test_bg_restricted_title) {
 
     override fun perform(activityResultLauncher: ActivityResultLauncher) {
         context.getSystemService()!!.apply {
diff --git a/vector/src/fdroid/java/im/vector/app/fdroid/receiver/AlarmSyncBroadcastReceiver.kt b/vector/src/fdroid/java/im/vector/app/fdroid/receiver/AlarmSyncBroadcastReceiver.kt
index c1fda2d404..4be36d7de3 100644
--- a/vector/src/fdroid/java/im/vector/app/fdroid/receiver/AlarmSyncBroadcastReceiver.kt
+++ b/vector/src/fdroid/java/im/vector/app/fdroid/receiver/AlarmSyncBroadcastReceiver.kt
@@ -27,7 +27,7 @@ import androidx.core.content.getSystemService
 import im.vector.app.core.extensions.singletonEntryPoint
 import im.vector.app.core.platform.PendingIntentCompat
 import im.vector.app.core.services.VectorSyncService
-import org.matrix.android.sdk.internal.session.sync.job.SyncService
+import org.matrix.android.sdk.api.session.sync.job.SyncService
 import timber.log.Timber
 
 class AlarmSyncBroadcastReceiver : BroadcastReceiver() {
diff --git a/vector/src/fdroid/java/im/vector/app/fdroid/receiver/OnApplicationUpgradeOrRebootReceiver.kt b/vector/src/fdroid/java/im/vector/app/fdroid/receiver/OnApplicationUpgradeOrRebootReceiver.kt
index 935d7e6e13..c1bc90c4db 100644
--- a/vector/src/fdroid/java/im/vector/app/fdroid/receiver/OnApplicationUpgradeOrRebootReceiver.kt
+++ b/vector/src/fdroid/java/im/vector/app/fdroid/receiver/OnApplicationUpgradeOrRebootReceiver.kt
@@ -29,10 +29,10 @@ class OnApplicationUpgradeOrRebootReceiver : BroadcastReceiver() {
     override fun onReceive(context: Context, intent: Intent) {
         Timber.v("## onReceive() ${intent.action}")
         val singletonEntryPoint = context.singletonEntryPoint()
-            BackgroundSyncStarter.start(
-                    context,
-                    singletonEntryPoint.vectorPreferences(),
-                    singletonEntryPoint.activeSessionHolder()
-            )
+        BackgroundSyncStarter.start(
+                context,
+                singletonEntryPoint.vectorPreferences(),
+                singletonEntryPoint.activeSessionHolder()
+        )
     }
 }
diff --git a/vector/src/gplay/java/im/vector/app/gplay/features/settings/troubleshoot/TestPlayServices.kt b/vector/src/gplay/java/im/vector/app/gplay/features/settings/troubleshoot/TestPlayServices.kt
index cc682e7a5f..ecb457bf9f 100644
--- a/vector/src/gplay/java/im/vector/app/gplay/features/settings/troubleshoot/TestPlayServices.kt
+++ b/vector/src/gplay/java/im/vector/app/gplay/features/settings/troubleshoot/TestPlayServices.kt
@@ -31,7 +31,7 @@ import javax.inject.Inject
  */
 class TestPlayServices @Inject constructor(private val context: FragmentActivity,
                                            private val stringProvider: StringProvider) :
-    TroubleshootTest(R.string.settings_troubleshoot_test_play_services_title) {
+        TroubleshootTest(R.string.settings_troubleshoot_test_play_services_title) {
 
     override fun perform(activityResultLauncher: ActivityResultLauncher) {
         val apiAvailability = GoogleApiAvailability.getInstance()
diff --git a/vector/src/gplay/java/im/vector/app/gplay/features/settings/troubleshoot/TestPushFromPushGateway.kt b/vector/src/gplay/java/im/vector/app/gplay/features/settings/troubleshoot/TestPushFromPushGateway.kt
index 7ae68b201b..f485de760a 100644
--- a/vector/src/gplay/java/im/vector/app/gplay/features/settings/troubleshoot/TestPushFromPushGateway.kt
+++ b/vector/src/gplay/java/im/vector/app/gplay/features/settings/troubleshoot/TestPushFromPushGateway.kt
@@ -41,7 +41,7 @@ class TestPushFromPushGateway @Inject constructor(private val context: FragmentA
                                                   private val errorFormatter: ErrorFormatter,
                                                   private val pushersManager: PushersManager,
                                                   private val activeSessionHolder: ActiveSessionHolder) :
-    TroubleshootTest(R.string.settings_troubleshoot_test_push_loop_title) {
+        TroubleshootTest(R.string.settings_troubleshoot_test_push_loop_title) {
 
     private var action: Job? = null
     private var pushReceived: Boolean = false
diff --git a/vector/src/gplay/java/im/vector/app/gplay/features/settings/troubleshoot/TestTokenRegistration.kt b/vector/src/gplay/java/im/vector/app/gplay/features/settings/troubleshoot/TestTokenRegistration.kt
index 913b5491ea..ec1b9ca7a2 100644
--- a/vector/src/gplay/java/im/vector/app/gplay/features/settings/troubleshoot/TestTokenRegistration.kt
+++ b/vector/src/gplay/java/im/vector/app/gplay/features/settings/troubleshoot/TestTokenRegistration.kt
@@ -37,7 +37,7 @@ class TestTokenRegistration @Inject constructor(private val context: FragmentAct
                                                 private val stringProvider: StringProvider,
                                                 private val pushersManager: PushersManager,
                                                 private val activeSessionHolder: ActiveSessionHolder) :
-    TroubleshootTest(R.string.settings_troubleshoot_test_token_registration_title) {
+        TroubleshootTest(R.string.settings_troubleshoot_test_token_registration_title) {
 
     override fun perform(activityResultLauncher: ActivityResultLauncher) {
         // Check if we have a registered pusher for this token
@@ -49,7 +49,7 @@ class TestTokenRegistration @Inject constructor(private val context: FragmentAct
             status = TestStatus.FAILED
             return
         }
-        val pushers = session.getPushers().filter {
+        val pushers = session.pushersService().getPushers().filter {
             it.pushKey == fcmToken && it.state == PusherState.REGISTERED
         }
         if (pushers.isEmpty()) {
diff --git a/vector/src/gplay/java/im/vector/app/gplay/push/fcm/VectorFirebaseMessagingService.kt b/vector/src/gplay/java/im/vector/app/gplay/push/fcm/VectorFirebaseMessagingService.kt
index 93a82f24f9..8003b63ba0 100755
--- a/vector/src/gplay/java/im/vector/app/gplay/push/fcm/VectorFirebaseMessagingService.kt
+++ b/vector/src/gplay/java/im/vector/app/gplay/push/fcm/VectorFirebaseMessagingService.kt
@@ -46,6 +46,7 @@ import kotlinx.coroutines.runBlocking
 import org.matrix.android.sdk.api.extensions.tryOrNull
 import org.matrix.android.sdk.api.logger.LoggerTag
 import org.matrix.android.sdk.api.session.Session
+import org.matrix.android.sdk.api.session.getRoom
 import timber.log.Timber
 import javax.inject.Inject
 
@@ -194,7 +195,7 @@ class VectorFirebaseMessagingService : FirebaseMessagingService() {
 
         coroutineScope.launch {
             Timber.tag(loggerTag.value).d("Fast lane: start request")
-            val event = tryOrNull { session.getEvent(roomId, eventId) } ?: return@launch
+            val event = tryOrNull { session.eventService().getEvent(roomId, eventId) } ?: return@launch
 
             val resolvedEvent = notifiableEventResolver.resolveInMemoryEvent(session, event, canBeReplaced = true)
 
diff --git a/vector/src/main/AndroidManifest.xml b/vector/src/main/AndroidManifest.xml
index 1d99fba91a..20b7c4908a 100644
--- a/vector/src/main/AndroidManifest.xml
+++ b/vector/src/main/AndroidManifest.xml
@@ -45,10 +45,11 @@
     
     
     
-    
+    
+    
 
     
-    
+    
 
     
     
@@ -84,8 +85,8 @@
         android:resizeableActivity="true"
         android:roundIcon="@mipmap/ic_launcher_round"
         android:supportsRtl="true"
-        android:theme="@style/Theme.Vector.Light"
         android:taskAffinity="${applicationId}.${appTaskAffinitySuffix}"
+        android:theme="@style/Theme.Vector.Light"
         tools:replace="android:allowBackup">
 
         
@@ -369,6 +370,17 @@
             
         
 
+        
+
+        
+
         
 
         
         Copyright (c) 2017-present, dialog LLC <info@dlg.im>
     
-    
  • - Armen101 / AudioRecordView -
    - Copyright 2019 Armen Gevorgyan -
  •  Apache License
    diff --git a/vector/src/main/java/im/vector/app/AppStateHandler.kt b/vector/src/main/java/im/vector/app/AppStateHandler.kt
    index 1ff3d97576..86a38ef71e 100644
    --- a/vector/src/main/java/im/vector/app/AppStateHandler.kt
    +++ b/vector/src/main/java/im/vector/app/AppStateHandler.kt
    @@ -18,9 +18,12 @@ package im.vector.app
     
     import androidx.lifecycle.DefaultLifecycleObserver
     import androidx.lifecycle.LifecycleOwner
    +import androidx.lifecycle.asFlow
     import arrow.core.Option
     import im.vector.app.core.di.ActiveSessionHolder
     import im.vector.app.core.utils.BehaviorDataSource
    +import im.vector.app.features.analytics.AnalyticsTracker
    +import im.vector.app.features.analytics.plan.UserProperties
     import im.vector.app.features.session.coroutineScope
     import im.vector.app.features.ui.UiStateRepository
     import kotlinx.coroutines.CoroutineScope
    @@ -28,12 +31,17 @@ import kotlinx.coroutines.Dispatchers
     import kotlinx.coroutines.SupervisorJob
     import kotlinx.coroutines.cancelChildren
     import kotlinx.coroutines.flow.distinctUntilChanged
    +import kotlinx.coroutines.flow.filterIsInstance
     import kotlinx.coroutines.flow.launchIn
    +import kotlinx.coroutines.flow.map
     import kotlinx.coroutines.flow.onEach
     import kotlinx.coroutines.launch
     import org.matrix.android.sdk.api.extensions.tryOrNull
     import org.matrix.android.sdk.api.session.Session
    +import org.matrix.android.sdk.api.session.getRoom
    +import org.matrix.android.sdk.api.session.getRoomSummary
     import org.matrix.android.sdk.api.session.group.model.GroupSummary
    +import org.matrix.android.sdk.api.session.initsync.SyncStatusService
     import org.matrix.android.sdk.api.session.room.model.RoomSummary
     import javax.inject.Inject
     import javax.inject.Singleton
    @@ -55,7 +63,8 @@ fun RoomGroupingMethod.group() = (this as? RoomGroupingMethod.ByLegacyGroup)?.gr
     class AppStateHandler @Inject constructor(
             private val sessionDataSource: ActiveSessionDataSource,
             private val uiStateRepository: UiStateRepository,
    -        private val activeSessionHolder: ActiveSessionHolder
    +        private val activeSessionHolder: ActiveSessionHolder,
    +        private val analyticsTracker: AnalyticsTracker
     ) : DefaultLifecycleObserver {
     
         private val coroutineScope = CoroutineScope(SupervisorJob() + Dispatchers.Main)
    @@ -71,7 +80,7 @@ class AppStateHandler @Inject constructor(
             return selectedSpaceDataSource.currentValue?.orNull()?.let {
                 if (it is RoomGroupingMethod.BySpace) {
                     // try to refresh sum?
    -                it.spaceSummary?.roomId?.let { activeSessionHolder.getSafeActiveSession()?.getRoomSummary(it) }?.let {
    +                it.spaceSummary?.roomId?.let { activeSessionHolder.getSafeActiveSession()?.roomService()?.getRoomSummary(it) }?.let {
                         RoomGroupingMethod.BySpace(it)
                     } ?: it
                 } else it
    @@ -103,12 +112,12 @@ class AppStateHandler @Inject constructor(
             val uSession = session ?: activeSessionHolder.getSafeActiveSession() ?: return
             if (selectedSpaceDataSource.currentValue?.orNull() is RoomGroupingMethod.ByLegacyGroup &&
                     groupId == selectedSpaceDataSource.currentValue?.orNull()?.group()?.groupId) return
    -        val activeGroup = groupId?.let { uSession.getGroupSummary(groupId) }
    +        val activeGroup = groupId?.let { uSession.groupService().getGroupSummary(groupId) }
             selectedSpaceDataSource.post(Option.just(RoomGroupingMethod.ByLegacyGroup(activeGroup)))
             if (groupId != null) {
                 uSession.coroutineScope.launch {
                     tryOrNull {
    -                    uSession.getGroup(groupId)?.fetchGroupData()
    +                    uSession.groupService().getGroup(groupId)?.fetchGroupData()
                     }
                 }
             }
    @@ -125,11 +134,23 @@ class AppStateHandler @Inject constructor(
                             } else {
                                 setCurrentGroup(uiStateRepository.getSelectedGroup(session.sessionId), session)
                             }
    +                        observeSyncStatus(session)
                         }
                     }
                     .launchIn(coroutineScope)
         }
     
    +    private fun observeSyncStatus(session: Session) {
    +        session.syncStatusService().getSyncStatusLive()
    +                .asFlow()
    +                .filterIsInstance()
    +                .map { session.spaceService().getRootSpaceSummaries().size }
    +                .distinctUntilChanged()
    +                .onEach { spacesNumber ->
    +                    analyticsTracker.updateUserProperties(UserProperties(numSpaces = spacesNumber))
    +                }.launchIn(session.coroutineScope)
    +    }
    +
         fun safeActiveSpaceId(): String? {
             return (selectedSpaceDataSource.currentValue?.orNull() as? RoomGroupingMethod.BySpace)?.spaceSummary?.roomId
         }
    diff --git a/vector/src/main/java/im/vector/app/AutoRageShaker.kt b/vector/src/main/java/im/vector/app/AutoRageShaker.kt
    index 43283254b1..c77ce2b647 100644
    --- a/vector/src/main/java/im/vector/app/AutoRageShaker.kt
    +++ b/vector/src/main/java/im/vector/app/AutoRageShaker.kt
    @@ -17,9 +17,11 @@
     package im.vector.app
     
     import android.content.SharedPreferences
    +import androidx.lifecycle.asFlow
     import im.vector.app.core.di.ActiveSessionHolder
     import im.vector.app.features.rageshake.BugReporter
     import im.vector.app.features.rageshake.ReportType
    +import im.vector.app.features.session.coroutineScope
     import im.vector.app.features.settings.VectorPreferences
     import kotlinx.coroutines.CoroutineScope
     import kotlinx.coroutines.Dispatchers
    @@ -34,6 +36,7 @@ import kotlinx.coroutines.launch
     import org.matrix.android.sdk.api.session.Session
     import org.matrix.android.sdk.api.session.events.model.Event
     import org.matrix.android.sdk.api.session.events.model.toContent
    +import org.matrix.android.sdk.api.session.initsync.SyncStatusService
     import timber.log.Timber
     import javax.inject.Inject
     import javax.inject.Singleton
    @@ -62,10 +65,11 @@ class AutoRageShaker @Inject constructor(
     
         private val e2eDetectedFlow = MutableSharedFlow(replay = 0)
         private val matchingRSRequestFlow = MutableSharedFlow(replay = 0)
    -
    +    private var hasSynced = false
    +    private var preferenceEnabled = false
         fun initialize() {
             observeActiveSession()
    -        enable(vectorPreferences.labsAutoReportUISI())
    +        preferenceEnabled = vectorPreferences.labsAutoReportUISI()
             // It's a singleton...
             vectorPreferences.subscribeToChanges(this)
     
    @@ -74,7 +78,7 @@ class AutoRageShaker @Inject constructor(
             e2eDetectedFlow
                     .onEach {
                         sendRageShake(it)
    -                    delay(2_000)
    +                    delay(60_000)
                     }
                     .catch { cause ->
                         Timber.w(cause, "Failed to RS")
    @@ -84,7 +88,7 @@ class AutoRageShaker @Inject constructor(
             matchingRSRequestFlow
                     .onEach {
                         sendMatchingRageShake(it)
    -                    delay(2_000)
    +                    delay(60_000)
                     }
                     .catch { cause ->
                         Timber.w(cause, "Failed to send matching rageshake")
    @@ -93,14 +97,7 @@ class AutoRageShaker @Inject constructor(
         }
     
         override fun onSharedPreferenceChanged(sharedPreferences: SharedPreferences?, key: String?) {
    -        enable(vectorPreferences.labsAutoReportUISI())
    -    }
    -
    -    var _enabled = false
    -    fun enable(enabled: Boolean) {
    -        if (enabled == _enabled) return
    -        _enabled = enabled
    -        detector.enabled = enabled
    +        preferenceEnabled = vectorPreferences.labsAutoReportUISI()
         }
     
         private fun observeActiveSession() {
    @@ -115,7 +112,6 @@ class AutoRageShaker @Inject constructor(
         }
     
         fun decryptionErrorDetected(target: E2EMessageDetected) {
    -        if (target.source == UISIEventSource.INITIAL_SYNC) return
             if (activeSessionHolder.getSafeActiveSession()?.sessionId != currentActiveSessionId) return
             val shouldSendRS = synchronized(alreadyReportedUisi) {
                 val reportInfo = ReportInfo(target.roomId, target.sessionId)
    @@ -148,7 +144,6 @@ class AutoRageShaker @Inject constructor(
                         append("\"room_id\": \"${target.roomId}\",")
                         append("\"sender_key\": \"${target.senderKey}\",")
                         append("\"device_id\": \"${target.senderDeviceId}\",")
    -                    append("\"source\": \"${target.source}\",")
                         append("\"user_id\": \"${target.senderUserId}\",")
                         append("\"session_id\": \"${target.sessionId}\"")
                         append("}")
    @@ -174,7 +169,7 @@ class AutoRageShaker @Inject constructor(
     
                             coroutineScope.launch {
                                 try {
    -                                activeSessionHolder.getSafeActiveSession()?.sendToDevice(
    +                                activeSessionHolder.getSafeActiveSession()?.toDeviceService()?.sendToDevice(
                                             eventType = AUTO_RS_REQUEST,
                                             userId = target.senderUserId,
                                             deviceId = target.senderDeviceId,
    @@ -245,6 +240,9 @@ class AutoRageShaker @Inject constructor(
                 override val reciprocateToDeviceEventType: String
                     get() = AUTO_RS_REQUEST
     
    +            override val enabled: Boolean
    +                get() = this@AutoRageShaker.preferenceEnabled && this@AutoRageShaker.hasSynced
    +
                 override fun uisiDetected(source: E2EMessageDetected) {
                     decryptionErrorDetected(source)
                 }
    @@ -261,14 +259,21 @@ class AutoRageShaker @Inject constructor(
                 return
             }
             this.currentActiveSessionId = sessionId
    -        this.detector.enabled = _enabled
    +
    +        hasSynced = session.hasAlreadySynced()
    +        session.syncStatusService().getSyncStatusLive()
    +                .asFlow()
    +                .onEach {
    +                    hasSynced = it !is SyncStatusService.Status.InitialSyncProgressing
    +                }
    +                .launchIn(session.coroutineScope)
             activeSessionIds.add(sessionId)
             session.addListener(this)
    -        session.addEventStreamListener(detector)
    +        session.eventStreamService().addEventStreamListener(detector)
         }
     
         override fun onSessionStopped(session: Session) {
    -        session.removeEventStreamListener(detector)
    +        session.eventStreamService().removeEventStreamListener(detector)
             activeSessionIds.remove(session.sessionId)
         }
     }
    diff --git a/vector/src/main/java/im/vector/app/UISIDetector.kt b/vector/src/main/java/im/vector/app/UISIDetector.kt
    index d6a4805e78..fea1861cc6 100644
    --- a/vector/src/main/java/im/vector/app/UISIDetector.kt
    +++ b/vector/src/main/java/im/vector/app/UISIDetector.kt
    @@ -16,33 +16,28 @@
     
     package im.vector.app
     
    +import org.matrix.android.sdk.api.extensions.orFalse
     import org.matrix.android.sdk.api.session.LiveEventListener
     import org.matrix.android.sdk.api.session.events.model.Event
    +import org.matrix.android.sdk.api.session.events.model.content.EncryptedEventContent
     import org.matrix.android.sdk.api.session.events.model.toModel
     import org.matrix.android.sdk.api.util.JsonDict
    -import org.matrix.android.sdk.internal.crypto.model.event.EncryptedEventContent
     import timber.log.Timber
     import java.util.Timer
     import java.util.TimerTask
     import java.util.concurrent.Executors
     
    -enum class UISIEventSource {
    -    INITIAL_SYNC,
    -    INCREMENTAL_SYNC,
    -    PAGINATION
    -}
    -
     data class E2EMessageDetected(
             val eventId: String,
             val roomId: String,
             val senderUserId: String,
             val senderDeviceId: String,
             val senderKey: String,
    -        val sessionId: String,
    -        val source: UISIEventSource) {
    +        val sessionId: String
    +) {
     
         companion object {
    -        fun fromEvent(event: Event, roomId: String, source: UISIEventSource): E2EMessageDetected {
    +        fun fromEvent(event: Event, roomId: String): E2EMessageDetected {
                 val encryptedContent = event.content.toModel()
     
                 return E2EMessageDetected(
    @@ -51,8 +46,7 @@ data class E2EMessageDetected(
                         senderUserId = event.senderId ?: "",
                         senderDeviceId = encryptedContent?.deviceId ?: "",
                         senderKey = encryptedContent?.senderKey ?: "",
    -                    sessionId = encryptedContent?.sessionId ?: "",
    -                    source = source
    +                    sessionId = encryptedContent?.sessionId ?: ""
                 )
             }
         }
    @@ -61,6 +55,7 @@ data class E2EMessageDetected(
     class UISIDetector : LiveEventListener {
     
         interface UISIDetectorCallback {
    +        val enabled: Boolean
             val reciprocateToDeviceEventType: String
             fun uisiDetected(source: E2EMessageDetected)
             fun uisiReciprocateRequest(source: Event)
    @@ -68,30 +63,16 @@ class UISIDetector : LiveEventListener {
     
         var callback: UISIDetectorCallback? = null
     
    -    private val trackedEvents = mutableListOf>()
    +    private val trackedEvents = mutableMapOf()
         private val executor = Executors.newSingleThreadExecutor()
         private val timer = Timer()
         private val timeoutMillis = 30_000L
    -    var enabled = false
    +    private val enabled: Boolean get() = callback?.enabled.orFalse()
     
    -    override fun onLiveEvent(roomId: String, event: Event) {
    -        if (!enabled) return
    -        if (!event.isEncrypted()) return
    -        executor.execute {
    -            handleEventReceived(E2EMessageDetected.fromEvent(event, roomId, UISIEventSource.INCREMENTAL_SYNC))
    -        }
    -    }
    -
    -    override fun onPaginatedEvent(roomId: String, event: Event) {
    -        if (!enabled) return
    -        if (!event.isEncrypted()) return
    -        executor.execute {
    -            handleEventReceived(E2EMessageDetected.fromEvent(event, roomId, UISIEventSource.PAGINATION))
    -        }
    -    }
    -
    -    override fun onEventDecrypted(eventId: String, roomId: String, clearEvent: JsonDict) {
    -        if (!enabled) return
    +    override fun onEventDecrypted(event: Event, clearEvent: JsonDict) {
    +        val eventId = event.eventId
    +        val roomId = event.roomId
    +        if (!enabled || eventId == null || roomId == null) return
             executor.execute {
                 unTrack(eventId, roomId)
             }
    @@ -104,57 +85,43 @@ class UISIDetector : LiveEventListener {
             }
         }
     
    -    override fun onEventDecryptionError(eventId: String, roomId: String, throwable: Throwable) {
    -        if (!enabled) return
    -        executor.execute {
    -            unTrack(eventId, roomId)?.let {
    -                triggerUISI(it)
    -            }
    -//            if (throwable is MXCryptoError.OlmError) {
    -//                if (throwable.olmException.message == "UNKNOWN_MESSAGE_INDEX") {
    -//                    unTrack(eventId, roomId)?.let {
    -//                        triggerUISI(it)
    -//                    }
    -//                }
    -//            }
    -        }
    -    }
    +    override fun onEventDecryptionError(event: Event, throwable: Throwable) {
    +        val eventId = event.eventId
    +        val roomId = event.roomId
    +        if (!enabled || eventId == null || roomId == null) return
     
    -    private fun handleEventReceived(detectorEvent: E2EMessageDetected) {
    -        if (!enabled) return
    -        if (trackedEvents.any { it.first == detectorEvent }) {
    -            Timber.w("## UISIDetector: Event ${detectorEvent.eventId} is already tracked")
    -        } else {
    -            // track it and start timer
    -            val timeoutTask = object : TimerTask() {
    -                override fun run() {
    -                    executor.execute {
    -                        unTrack(detectorEvent.eventId, detectorEvent.roomId)
    -                        Timber.v("## UISIDetector: Timeout on ${detectorEvent.eventId} ")
    -                        triggerUISI(detectorEvent)
    -                    }
    +        val trackerId: String = trackerId(eventId, roomId)
    +        if (trackedEvents.containsKey(trackerId)) {
    +            Timber.w("## UISIDetector: Event $eventId is already tracked")
    +            return
    +        }
    +        // track it and start timer
    +        val timeoutTask = object : TimerTask() {
    +            override fun run() {
    +                executor.execute {
    +                    unTrack(eventId, roomId)
    +                    Timber.v("## UISIDetector: Timeout on $eventId")
    +                    triggerUISI(E2EMessageDetected.fromEvent(event, roomId))
                     }
                 }
    -            trackedEvents.add(detectorEvent to timeoutTask)
    -            timer.schedule(timeoutTask, timeoutMillis)
             }
    +        trackedEvents[trackerId] = timeoutTask
    +        timer.schedule(timeoutTask, timeoutMillis)
         }
     
    +    override fun onLiveEvent(roomId: String, event: Event) {}
    +
    +    override fun onPaginatedEvent(roomId: String, event: Event) {}
    +
    +    private fun trackerId(eventId: String, roomId: String): String = "$roomId-$eventId"
    +
         private fun triggerUISI(source: E2EMessageDetected) {
             if (!enabled) return
             Timber.i("## UISIDetector: Unable To Decrypt $source")
             callback?.uisiDetected(source)
         }
     
    -    private fun unTrack(eventId: String, roomId: String): E2EMessageDetected? {
    -        val index = trackedEvents.indexOfFirst { it.first.eventId == eventId && it.first.roomId == roomId }
    -        return if (index != -1) {
    -            trackedEvents.removeAt(index).let {
    -                it.second.cancel()
    -                it.first
    -            }
    -        } else {
    -            null
    -        }
    +    private fun unTrack(eventId: String, roomId: String) {
    +        trackedEvents.remove(trackerId(eventId, roomId))?.cancel()
         }
     }
    diff --git a/vector/src/main/java/im/vector/app/VectorApplication.kt b/vector/src/main/java/im/vector/app/VectorApplication.kt
    index a3f4ffcfcd..e12eecfefc 100644
    --- a/vector/src/main/java/im/vector/app/VectorApplication.kt
    +++ b/vector/src/main/java/im/vector/app/VectorApplication.kt
    @@ -230,13 +230,13 @@ class VectorApplication :
             val sdkVersion = Matrix.getSdkVersion()
             val date = SimpleDateFormat("MM-dd HH:mm:ss.SSSZ", Locale.US).format(Date())
     
    -        Timber.v("----------------------------------------------------------------")
    -        Timber.v("----------------------------------------------------------------")
    -        Timber.v(" Application version: $appVersion")
    -        Timber.v(" SDK version: $sdkVersion")
    -        Timber.v(" Local time: $date")
    -        Timber.v("----------------------------------------------------------------")
    -        Timber.v("----------------------------------------------------------------\n\n\n\n")
    +        Timber.d("----------------------------------------------------------------")
    +        Timber.d("----------------------------------------------------------------")
    +        Timber.d(" Application version: $appVersion")
    +        Timber.d(" SDK version: $sdkVersion")
    +        Timber.d(" Local time: $date")
    +        Timber.d("----------------------------------------------------------------")
    +        Timber.d("----------------------------------------------------------------\n\n\n\n")
         }
     
         override fun attachBaseContext(base: Context) {
    diff --git a/vector/src/main/java/im/vector/app/core/animations/Konfetti.kt b/vector/src/main/java/im/vector/app/core/animations/Konfetti.kt
    index 22764ac5bd..882891810b 100644
    --- a/vector/src/main/java/im/vector/app/core/animations/Konfetti.kt
    +++ b/vector/src/main/java/im/vector/app/core/animations/Konfetti.kt
    @@ -20,9 +20,14 @@ import android.content.Context
     import androidx.annotation.ColorInt
     import androidx.core.content.ContextCompat
     import im.vector.app.R
    -import nl.dionsegijn.konfetti.KonfettiView
    -import nl.dionsegijn.konfetti.models.Shape
    -import nl.dionsegijn.konfetti.models.Size
    +import nl.dionsegijn.konfetti.core.Angle
    +import nl.dionsegijn.konfetti.core.Party
    +import nl.dionsegijn.konfetti.core.Position
    +import nl.dionsegijn.konfetti.core.Spread
    +import nl.dionsegijn.konfetti.core.emitter.Emitter
    +import nl.dionsegijn.konfetti.core.models.Shape
    +import nl.dionsegijn.konfetti.core.models.Size
    +import nl.dionsegijn.konfetti.xml.KonfettiView
     
     fun KonfettiView.play() {
         val confettiColors = listOf(
    @@ -35,16 +40,22 @@ fun KonfettiView.play() {
                 R.color.palette_prune,
                 R.color.palette_kiwi
         )
    -    build()
    -            .addColors(confettiColors.toColorInt(context))
    -            .setDirection(0.0, 359.0)
    -            .setSpeed(2f, 5f)
    -            .setFadeOutEnabled(true)
    -            .setTimeToLive(2000L)
    -            .addShapes(Shape.Square, Shape.Circle)
    -            .addSizes(Size(12))
    -            .setPosition(-50f, width + 50f, -50f, -50f)
    -            .streamFor(150, 3000L)
    +    val emitterConfig = Emitter(2000).perSecond(100)
    +    val party = Party(
    +            emitter = emitterConfig,
    +            colors = confettiColors.toColorInt(context),
    +            angle = Angle.Companion.BOTTOM,
    +            spread = Spread.ROUND,
    +            shapes = listOf(Shape.Square, Shape.Circle),
    +            size = listOf(Size(12)),
    +            speed = 2f,
    +            maxSpeed = 5f,
    +            fadeOutEnabled = true,
    +            timeToLive = 2000L,
    +            position = Position.Relative(0.0, 0.0).between(Position.Relative(1.0, 0.0)),
    +    )
    +    reset()
    +    start(party)
     }
     
     @ColorInt
    diff --git a/vector/src/main/java/im/vector/app/core/date/DefaultDateFormatterProvider.kt b/vector/src/main/java/im/vector/app/core/date/DefaultDateFormatterProvider.kt
    index 6371035a17..361868cd95 100644
    --- a/vector/src/main/java/im/vector/app/core/date/DefaultDateFormatterProvider.kt
    +++ b/vector/src/main/java/im/vector/app/core/date/DefaultDateFormatterProvider.kt
    @@ -24,7 +24,7 @@ import javax.inject.Inject
     
     class DefaultDateFormatterProvider @Inject constructor(private val context: Context,
                                                            private val localeProvider: LocaleProvider) :
    -    DateFormatterProvider {
    +        DateFormatterProvider {
     
         override val dateWithMonthFormatter: DateTimeFormatter by lazy {
             val pattern = DateFormat.getBestDateTimePattern(localeProvider.current(), "d MMMMM")
    diff --git a/vector/src/main/java/im/vector/app/core/di/FragmentModule.kt b/vector/src/main/java/im/vector/app/core/di/FragmentModule.kt
    index 4dcfbe16f8..e3e64063f3 100644
    --- a/vector/src/main/java/im/vector/app/core/di/FragmentModule.kt
    +++ b/vector/src/main/java/im/vector/app/core/di/FragmentModule.kt
    @@ -102,6 +102,7 @@ import im.vector.app.features.onboarding.ftueauth.FtueAuthCaptchaFragment
     import im.vector.app.features.onboarding.ftueauth.FtueAuthChooseDisplayNameFragment
     import im.vector.app.features.onboarding.ftueauth.FtueAuthChooseProfilePictureFragment
     import im.vector.app.features.onboarding.ftueauth.FtueAuthGenericTextInputFormFragment
    +import im.vector.app.features.onboarding.ftueauth.FtueAuthLegacyStyleCaptchaFragment
     import im.vector.app.features.onboarding.ftueauth.FtueAuthLoginFragment
     import im.vector.app.features.onboarding.ftueauth.FtueAuthPersonalizationCompleteFragment
     import im.vector.app.features.onboarding.ftueauth.FtueAuthResetPasswordFragment
    @@ -114,6 +115,7 @@ import im.vector.app.features.onboarding.ftueauth.FtueAuthSplashFragment
     import im.vector.app.features.onboarding.ftueauth.FtueAuthUseCaseFragment
     import im.vector.app.features.onboarding.ftueauth.FtueAuthWaitForEmailFragment
     import im.vector.app.features.onboarding.ftueauth.FtueAuthWebFragment
    +import im.vector.app.features.onboarding.ftueauth.terms.FtueAuthLegacyStyleTermsFragment
     import im.vector.app.features.onboarding.ftueauth.terms.FtueAuthTermsFragment
     import im.vector.app.features.pin.PinFragment
     import im.vector.app.features.poll.create.CreatePollFragment
    @@ -407,6 +409,11 @@ interface FragmentModule {
         @FragmentKey(LoginWaitForEmailFragment2::class)
         fun bindLoginWaitForEmailFragment2(fragment: LoginWaitForEmailFragment2): Fragment
     
    +    @Binds
    +    @IntoMap
    +    @FragmentKey(FtueAuthLegacyStyleCaptchaFragment::class)
    +    fun bindFtueAuthLegacyStyleCaptchaFragment(fragment: FtueAuthLegacyStyleCaptchaFragment): Fragment
    +
         @Binds
         @IntoMap
         @FragmentKey(FtueAuthCaptchaFragment::class)
    @@ -472,6 +479,11 @@ interface FragmentModule {
         @FragmentKey(FtueAuthWebFragment::class)
         fun bindFtueAuthWebFragment(fragment: FtueAuthWebFragment): Fragment
     
    +    @Binds
    +    @IntoMap
    +    @FragmentKey(FtueAuthLegacyStyleTermsFragment::class)
    +    fun bindFtueAuthLegacyStyleTermsFragment(fragment: FtueAuthLegacyStyleTermsFragment): Fragment
    +
         @Binds
         @IntoMap
         @FragmentKey(FtueAuthTermsFragment::class)
    diff --git a/vector/src/main/java/im/vector/app/core/di/HiltMavericksViewModelFactory.kt b/vector/src/main/java/im/vector/app/core/di/HiltMavericksViewModelFactory.kt
    index 13702053e8..5850cb8cb9 100644
    --- a/vector/src/main/java/im/vector/app/core/di/HiltMavericksViewModelFactory.kt
    +++ b/vector/src/main/java/im/vector/app/core/di/HiltMavericksViewModelFactory.kt
    @@ -45,7 +45,7 @@ import dagger.hilt.components.SingletonComponent
     inline fun , S : MavericksState> hiltMavericksViewModelFactory() = HiltMavericksViewModelFactory(VM::class.java)
     
     class HiltMavericksViewModelFactory, S : MavericksState>(
    -    private val viewModelClass: Class>
    +        private val viewModelClass: Class>
     ) : MavericksViewModelFactory {
     
         override fun create(viewModelContext: ViewModelContext, state: S): VM {
    diff --git a/vector/src/main/java/im/vector/app/core/di/SingletonModule.kt b/vector/src/main/java/im/vector/app/core/di/SingletonModule.kt
    index e3a84f95de..0db7e4e8ea 100644
    --- a/vector/src/main/java/im/vector/app/core/di/SingletonModule.kt
    +++ b/vector/src/main/java/im/vector/app/core/di/SingletonModule.kt
    @@ -46,6 +46,7 @@ import im.vector.app.features.navigation.Navigator
     import im.vector.app.features.pin.PinCodeStore
     import im.vector.app.features.pin.SharedPrefPinCodeStore
     import im.vector.app.features.room.VectorRoomDisplayNameFallbackProvider
    +import im.vector.app.features.settings.VectorPreferences
     import im.vector.app.features.ui.SharedPreferencesUiStateRepository
     import im.vector.app.features.ui.UiStateRepository
     import kotlinx.coroutines.CoroutineScope
    @@ -59,6 +60,7 @@ import org.matrix.android.sdk.api.auth.HomeServerHistoryService
     import org.matrix.android.sdk.api.legacy.LegacySessionImporter
     import org.matrix.android.sdk.api.raw.RawService
     import org.matrix.android.sdk.api.session.Session
    +import org.matrix.android.sdk.api.settings.LightweightSettingsStorage
     import javax.inject.Singleton
     
     @InstallIn(SingletonComponent::class)
    @@ -113,11 +115,13 @@ object VectorStaticModule {
         }
     
         @Provides
    -    fun providesMatrixConfiguration(vectorRoomDisplayNameFallbackProvider: VectorRoomDisplayNameFallbackProvider): MatrixConfiguration {
    +    fun providesMatrixConfiguration(
    +            vectorPreferences: VectorPreferences,
    +            vectorRoomDisplayNameFallbackProvider: VectorRoomDisplayNameFallbackProvider): MatrixConfiguration {
             return MatrixConfiguration(
                     applicationFlavor = BuildConfig.FLAVOR_DESCRIPTION,
                     roomDisplayNameFallbackProvider = vectorRoomDisplayNameFallbackProvider,
    -                presenceSyncEnabled = BuildConfig.PRESENCE_SYNC_ENABLED
    +                threadMessagesEnabledDefault = vectorPreferences.areThreadMessagesEnabled(),
             )
         }
     
    @@ -148,6 +152,11 @@ object VectorStaticModule {
             return matrix.rawService()
         }
     
    +    @Provides
    +    fun providesLightweightSettingsStorage(matrix: Matrix): LightweightSettingsStorage {
    +        return matrix.lightweightSettingsStorage()
    +    }
    +
         @Provides
         fun providesHomeServerHistoryService(matrix: Matrix): HomeServerHistoryService {
             return matrix.homeServerHistoryService()
    diff --git a/vector/src/main/java/im/vector/app/core/dialogs/ExportKeysDialog.kt b/vector/src/main/java/im/vector/app/core/dialogs/ExportKeysDialog.kt
    index b87b7f0286..024d283c4c 100644
    --- a/vector/src/main/java/im/vector/app/core/dialogs/ExportKeysDialog.kt
    +++ b/vector/src/main/java/im/vector/app/core/dialogs/ExportKeysDialog.kt
    @@ -35,7 +35,7 @@ class ExportKeysDialog {
             val textWatcher = object : SimpleTextWatcher() {
                 override fun afterTextChanged(s: Editable) {
                     when {
    -                    views.exportDialogEt.text.isNullOrEmpty()                                   -> {
    +                    views.exportDialogEt.text.isNullOrEmpty()                                           -> {
                             views.exportDialogSubmit.isEnabled = false
                             views.exportDialogTilConfirm.error = null
                         }
    @@ -43,7 +43,7 @@ class ExportKeysDialog {
                             views.exportDialogSubmit.isEnabled = true
                             views.exportDialogTilConfirm.error = null
                         }
    -                    else                                                                       -> {
    +                    else                                                                                -> {
                             views.exportDialogSubmit.isEnabled = false
                             views.exportDialogTilConfirm.error = activity.getString(R.string.passphrase_passphrase_does_not_match)
                         }
    diff --git a/vector/src/main/java/im/vector/app/core/dialogs/ManuallyVerifyDialog.kt b/vector/src/main/java/im/vector/app/core/dialogs/ManuallyVerifyDialog.kt
    index 9e318bf693..f3923fc4e0 100644
    --- a/vector/src/main/java/im/vector/app/core/dialogs/ManuallyVerifyDialog.kt
    +++ b/vector/src/main/java/im/vector/app/core/dialogs/ManuallyVerifyDialog.kt
    @@ -21,7 +21,7 @@ import com.google.android.material.dialog.MaterialAlertDialogBuilder
     import im.vector.app.R
     import im.vector.app.databinding.DialogDeviceVerifyBinding
     import org.matrix.android.sdk.api.extensions.getFingerprintHumanReadable
    -import org.matrix.android.sdk.internal.crypto.model.CryptoDeviceInfo
    +import org.matrix.android.sdk.api.session.crypto.model.CryptoDeviceInfo
     
     object ManuallyVerifyDialog {
     
    diff --git a/vector/src/main/java/im/vector/app/core/dialogs/UnrecognizedCertificateDialog.kt b/vector/src/main/java/im/vector/app/core/dialogs/UnrecognizedCertificateDialog.kt
    index f46737d6c6..415c82b330 100644
    --- a/vector/src/main/java/im/vector/app/core/dialogs/UnrecognizedCertificateDialog.kt
    +++ b/vector/src/main/java/im/vector/app/core/dialogs/UnrecognizedCertificateDialog.kt
    @@ -21,10 +21,8 @@ import im.vector.app.R
     import im.vector.app.core.di.ActiveSessionHolder
     import im.vector.app.core.resources.StringProvider
     import im.vector.app.databinding.DialogSslFingerprintBinding
    -import org.matrix.android.sdk.internal.network.ssl.Fingerprint
    +import org.matrix.android.sdk.api.network.ssl.Fingerprint
     import timber.log.Timber
    -import java.util.HashMap
    -import java.util.HashSet
     import javax.inject.Inject
     
     /**
    diff --git a/vector/src/main/java/im/vector/app/core/epoxy/bottomsheet/BottomSheetActionItem.kt b/vector/src/main/java/im/vector/app/core/epoxy/bottomsheet/BottomSheetActionItem.kt
    index 854a5d3419..ed3d55fca9 100644
    --- a/vector/src/main/java/im/vector/app/core/epoxy/bottomsheet/BottomSheetActionItem.kt
    +++ b/vector/src/main/java/im/vector/app/core/epoxy/bottomsheet/BottomSheetActionItem.kt
    @@ -71,6 +71,9 @@ abstract class BottomSheetActionItem : VectorEpoxyModel(R.id.actionIcon)
             val text by bind(R.id.actionTitle)
             val selected by bind(R.id.actionSelected)
    +        val betaLabel by bind(R.id.actionBetaTextView)
         }
     }
    diff --git a/vector/src/main/java/im/vector/app/core/epoxy/bottomsheet/BottomSheetMessagePreviewItem.kt b/vector/src/main/java/im/vector/app/core/epoxy/bottomsheet/BottomSheetMessagePreviewItem.kt
    index 14ba34cc52..b90956ad9e 100644
    --- a/vector/src/main/java/im/vector/app/core/epoxy/bottomsheet/BottomSheetMessagePreviewItem.kt
    +++ b/vector/src/main/java/im/vector/app/core/epoxy/bottomsheet/BottomSheetMessagePreviewItem.kt
    @@ -33,7 +33,7 @@ import im.vector.app.core.extensions.setTextOrHide
     import im.vector.app.core.glide.GlideApp
     import im.vector.app.features.displayname.getBestName
     import im.vector.app.features.home.AvatarRenderer
    -import im.vector.app.features.home.room.detail.timeline.helper.LocationPinProvider
    +import im.vector.app.features.home.room.detail.timeline.action.LocationUiData
     import im.vector.app.features.home.room.detail.timeline.item.BindingOptions
     import im.vector.app.features.home.room.detail.timeline.tools.findPillsAndProcess
     import im.vector.app.features.media.ImageContentRenderer
    @@ -71,13 +71,7 @@ abstract class BottomSheetMessagePreviewItem : VectorEpoxyModel
                 GlideApp.with(holder.staticMapImageView)
    -                    .load(locationUrl)
    +                    .load(safeLocationUiData.locationUrl)
                         .apply(RequestOptions.centerCropTransform())
                         .into(holder.staticMapImageView)
     
    -            locationPinProvider?.create(locationOwnerId) { pinDrawable ->
    +            safeLocationUiData.locationPinProvider.create(safeLocationUiData.locationOwnerId) { pinDrawable ->
                     GlideApp.with(holder.staticMapPinImageView)
                             .load(pinDrawable)
                             .into(holder.staticMapPinImageView)
    diff --git a/vector/src/main/java/im/vector/app/core/epoxy/profiles/BaseProfileMatrixItem.kt b/vector/src/main/java/im/vector/app/core/epoxy/profiles/BaseProfileMatrixItem.kt
    index e4b6124e19..956e1de92c 100644
    --- a/vector/src/main/java/im/vector/app/core/epoxy/profiles/BaseProfileMatrixItem.kt
    +++ b/vector/src/main/java/im/vector/app/core/epoxy/profiles/BaseProfileMatrixItem.kt
    @@ -25,7 +25,7 @@ import im.vector.app.core.epoxy.onClick
     import im.vector.app.core.extensions.setTextOrHide
     import im.vector.app.features.displayname.getBestName
     import im.vector.app.features.home.AvatarRenderer
    -import org.matrix.android.sdk.api.crypto.RoomEncryptionTrustLevel
    +import org.matrix.android.sdk.api.session.crypto.model.RoomEncryptionTrustLevel
     import org.matrix.android.sdk.api.util.MatrixItem
     
     abstract class BaseProfileMatrixItem : VectorEpoxyModel() {
    diff --git a/vector/src/main/java/im/vector/app/core/epoxy/profiles/ProfileMatrixItem.kt b/vector/src/main/java/im/vector/app/core/epoxy/profiles/ProfileMatrixItem.kt
    index 0af342641e..90e81ceb26 100644
    --- a/vector/src/main/java/im/vector/app/core/epoxy/profiles/ProfileMatrixItem.kt
    +++ b/vector/src/main/java/im/vector/app/core/epoxy/profiles/ProfileMatrixItem.kt
    @@ -32,6 +32,7 @@ abstract class ProfileMatrixItem : BaseProfileMatrixItem(R.id.matrixItemTitle)
             val subtitleView by bind(R.id.matrixItemSubtitle)
    +        val ignoredUserView by bind(R.id.matrixItemIgnored)
             val powerLabel by bind(R.id.matrixItemPowerLevelLabel)
             val presenceImageView by bind(R.id.matrixItemPresenceImageView)
             val avatarImageView by bind(R.id.matrixItemAvatar)
    diff --git a/vector/src/main/java/im/vector/app/core/epoxy/profiles/ProfileMatrixItemWithPowerLevel.kt b/vector/src/main/java/im/vector/app/core/epoxy/profiles/ProfileMatrixItemWithPowerLevel.kt
    index 12189dc8f4..453f402496 100644
    --- a/vector/src/main/java/im/vector/app/core/epoxy/profiles/ProfileMatrixItemWithPowerLevel.kt
    +++ b/vector/src/main/java/im/vector/app/core/epoxy/profiles/ProfileMatrixItemWithPowerLevel.kt
    @@ -26,11 +26,13 @@ import im.vector.app.core.extensions.setTextOrHide
     @EpoxyModelClass(layout = R.layout.item_profile_matrix_item)
     abstract class ProfileMatrixItemWithPowerLevel : ProfileMatrixItem() {
     
    +    @EpoxyAttribute var ignoredUser: Boolean = false
         @EpoxyAttribute var powerLevelLabel: CharSequence? = null
     
         override fun bind(holder: Holder) {
             super.bind(holder)
             holder.editableView.isVisible = false
    +        holder.ignoredUserView.isVisible = ignoredUser
             holder.powerLabel.setTextOrHide(powerLevelLabel)
         }
     }
    diff --git a/vector/src/main/java/im/vector/app/core/extensions/Collections.kt b/vector/src/main/java/im/vector/app/core/extensions/Collections.kt
    index 5168915c9c..d48875983d 100644
    --- a/vector/src/main/java/im/vector/app/core/extensions/Collections.kt
    +++ b/vector/src/main/java/im/vector/app/core/extensions/Collections.kt
    @@ -18,3 +18,5 @@ package im.vector.app.core.extensions
     
     inline fun  List.nextOrNull(index: Int) = getOrNull(index + 1)
     inline fun  List.prevOrNull(index: Int) = getOrNull(index - 1)
    +
    +fun  List.containsAllItems(vararg items: T) = this.containsAll(items.toList())
    diff --git a/vector/src/main/java/im/vector/app/core/extensions/ConstraintLayout.kt b/vector/src/main/java/im/vector/app/core/extensions/ConstraintLayout.kt
    index b1b30da156..b88e315978 100644
    --- a/vector/src/main/java/im/vector/app/core/extensions/ConstraintLayout.kt
    +++ b/vector/src/main/java/im/vector/app/core/extensions/ConstraintLayout.kt
    @@ -16,8 +16,12 @@
     
     package im.vector.app.core.extensions
     
    +import android.view.View
     import androidx.constraintlayout.widget.ConstraintLayout
     import androidx.constraintlayout.widget.ConstraintSet
    +import androidx.core.view.children
    +import androidx.core.view.doOnLayout
    +import kotlin.math.roundToInt
     
     fun ConstraintLayout.updateConstraintSet(block: (ConstraintSet) -> Unit) {
         ConstraintSet().let {
    @@ -26,3 +30,21 @@ fun ConstraintLayout.updateConstraintSet(block: (ConstraintSet) -> Unit) {
             it.applyTo(this)
         }
     }
    +
    +/**
    + * Helper to recalculate all ConstraintLayout child views with percentage based height against the parent's height.
    + * This is helpful when using a ConstraintLayout within a ScrollView as any percentages will use the total scrolling size
    + * instead of the viewport/ScrollView height
    + */
    +fun ConstraintLayout.realignPercentagesToParent() {
    +    doOnLayout {
    +        val rootHeight = (parent as View).height
    +        children.forEach { child ->
    +            val params = child.layoutParams as ConstraintLayout.LayoutParams
    +            if (params.matchConstraintPercentHeight != 1.0f) {
    +                params.height = (rootHeight * params.matchConstraintPercentHeight).roundToInt()
    +                child.layoutParams = params
    +            }
    +        }
    +    }
    +}
    diff --git a/vector/src/main/java/im/vector/app/core/extensions/Context.kt b/vector/src/main/java/im/vector/app/core/extensions/Context.kt
    index b1e24c9502..0f785e43a3 100644
    --- a/vector/src/main/java/im/vector/app/core/extensions/Context.kt
    +++ b/vector/src/main/java/im/vector/app/core/extensions/Context.kt
    @@ -18,6 +18,7 @@ package im.vector.app.core.extensions
     
     import android.content.Context
     import android.graphics.drawable.Drawable
    +import android.net.Uri
     import android.text.Spannable
     import android.text.SpannableString
     import android.text.style.ImageSpan
    @@ -31,6 +32,7 @@ import androidx.datastore.preferences.core.Preferences
     import dagger.hilt.EntryPoints
     import im.vector.app.core.datastore.dataStoreProvider
     import im.vector.app.core.di.SingletonEntryPoint
    +import java.io.OutputStream
     import kotlin.math.roundToInt
     
     fun Context.singletonEntryPoint(): SingletonEntryPoint {
    @@ -68,3 +70,10 @@ private fun Float.toAndroidAlpha(): Int {
     }
     
     val Context.dataStoreProvider: (String) -> DataStore by dataStoreProvider()
    +
    +/**
    + * Open Uri in truncate mode to make sure we don't partially overwrite content when we get passed a Uri to an existing file.
    + */
    +fun Context.safeOpenOutputStream(uri: Uri): OutputStream? {
    +    return contentResolver.openOutputStream(uri, "wt")
    +}
    diff --git a/vector/src/main/java/im/vector/app/core/extensions/ResultExtensions.kt b/vector/src/main/java/im/vector/app/core/extensions/ResultExtensions.kt
    new file mode 100644
    index 0000000000..bb2e9070ca
    --- /dev/null
    +++ b/vector/src/main/java/im/vector/app/core/extensions/ResultExtensions.kt
    @@ -0,0 +1,25 @@
    +/*
    + * Copyright (c) 2022 New Vector Ltd
    + *
    + * Licensed under the Apache License, Version 2.0 (the "License");
    + * you may not use this file except in compliance with the License.
    + * You may obtain a copy of the License at
    + *
    + *     http://www.apache.org/licenses/LICENSE-2.0
    + *
    + * Unless required by applicable law or agreed to in writing, software
    + * distributed under the License is distributed on an "AS IS" BASIS,
    + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    + * See the License for the specific language governing permissions and
    + * limitations under the License.
    + */
    +
    +package im.vector.app.core.extensions
    +
    +@Suppress("UNCHECKED_CAST") // We're casting null failure results to R
    +inline fun  Result.andThen(block: (T) -> Result): Result {
    +    return when (val result = getOrNull()) {
    +        null -> this as Result
    +        else -> block(result)
    +    }
    +}
    diff --git a/vector/src/main/java/im/vector/app/core/extensions/Session.kt b/vector/src/main/java/im/vector/app/core/extensions/Session.kt
    index 87ed51522f..9bc1aff868 100644
    --- a/vector/src/main/java/im/vector/app/core/extensions/Session.kt
    +++ b/vector/src/main/java/im/vector/app/core/extensions/Session.kt
    @@ -30,11 +30,11 @@ import timber.log.Timber
     fun Session.configureAndStart(context: Context, startSyncing: Boolean = true) {
         Timber.i("Configure and start session for $myUserId")
         open()
    -    setFilter(FilterService.FilterPreset.ElementFilter)
    +    filterService().setFilter(FilterService.FilterPreset.ElementFilter)
         if (startSyncing) {
             startSyncing(context)
         }
    -    refreshPushers()
    +    pushersService().refreshPushers()
         context.singletonEntryPoint().webRtcCallManager().checkForProtocolsSupportIfNeeded()
     }
     
    @@ -74,8 +74,8 @@ fun Session.cannotLogoutSafely(): Boolean {
         return hasUnsavedKeys() ||
                 // has local cross signing keys
                 (cryptoService().crossSigningService().allPrivateKeysKnown() &&
    -            // That are not backed up
    -            !sharedSecretStorageService.isRecoverySetup())
    +                    // That are not backed up
    +                    !sharedSecretStorageService().isRecoverySetup())
     }
     
     fun Session.vectorStore(context: Context) = VectorSessionStore(context, myUserId)
    diff --git a/vector/src/main/java/im/vector/app/core/extensions/TextInputLayout.kt b/vector/src/main/java/im/vector/app/core/extensions/TextInputLayout.kt
    new file mode 100644
    index 0000000000..4347da71f0
    --- /dev/null
    +++ b/vector/src/main/java/im/vector/app/core/extensions/TextInputLayout.kt
    @@ -0,0 +1,32 @@
    +/*
    + * Copyright (c) 2022 New Vector Ltd
    + *
    + * Licensed under the Apache License, Version 2.0 (the "License");
    + * you may not use this file except in compliance with the License.
    + * You may obtain a copy of the License at
    + *
    + *     http://www.apache.org/licenses/LICENSE-2.0
    + *
    + * Unless required by applicable law or agreed to in writing, software
    + * distributed under the License is distributed on an "AS IS" BASIS,
    + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    + * See the License for the specific language governing permissions and
    + * limitations under the License.
    + */
    +
    +package im.vector.app.core.extensions
    +
    +import com.google.android.material.textfield.TextInputLayout
    +import kotlinx.coroutines.flow.map
    +import reactivecircus.flowbinding.android.widget.textChanges
    +
    +fun TextInputLayout.editText() = this.editText!!
    +
    +/**
    + * Detect if a field starts or ends with spaces
    + */
    +fun TextInputLayout.hasSurroundingSpaces() = editText().text.toString().let { it.trim() != it }
    +
    +fun TextInputLayout.hasContentFlow(mapper: (CharSequence) -> CharSequence = { it }) = editText().textChanges().map { mapper(it).isNotEmpty() }
    +
    +fun TextInputLayout.content() = editText().text.toString()
    diff --git a/vector/src/main/java/im/vector/app/core/extensions/UrlExtensions.kt b/vector/src/main/java/im/vector/app/core/extensions/UrlExtensions.kt
    index 5037f78445..749da0d987 100644
    --- a/vector/src/main/java/im/vector/app/core/extensions/UrlExtensions.kt
    +++ b/vector/src/main/java/im/vector/app/core/extensions/UrlExtensions.kt
    @@ -19,8 +19,8 @@ package im.vector.app.core.extensions
     /**
      * Ex: "https://matrix.org/" -> "matrix.org"
      */
    -fun String?.toReducedUrl(): String {
    +fun String?.toReducedUrl(keepSchema: Boolean = false): String {
         return (this ?: "")
    -            .substringAfter("://")
    +            .run { if (keepSchema) this else substringAfter("://") }
                 .trim { it == '/' }
     }
    diff --git a/vector/src/main/java/im/vector/app/core/extensions/View.kt b/vector/src/main/java/im/vector/app/core/extensions/View.kt
    index 7ec86d3508..4b21063f0b 100644
    --- a/vector/src/main/java/im/vector/app/core/extensions/View.kt
    +++ b/vector/src/main/java/im/vector/app/core/extensions/View.kt
    @@ -32,3 +32,12 @@ fun View.showKeyboard(andRequestFocus: Boolean = false) {
         val imm = context?.getSystemService()
         imm?.showSoftInput(this, InputMethodManager.SHOW_IMPLICIT)
     }
    +
    +fun View.setHorizontalPadding(padding: Int) {
    +    setPadding(
    +            padding,
    +            paddingTop,
    +            padding,
    +            paddingBottom
    +    )
    +}
    diff --git a/vector/src/main/java/im/vector/app/core/glide/AvatarPlaceholder.kt b/vector/src/main/java/im/vector/app/core/glide/AvatarPlaceholder.kt
    index e61f81de55..e7ced6b335 100644
    --- a/vector/src/main/java/im/vector/app/core/glide/AvatarPlaceholder.kt
    +++ b/vector/src/main/java/im/vector/app/core/glide/AvatarPlaceholder.kt
    @@ -43,7 +43,7 @@ class AvatarPlaceholderModelLoaderFactory(private val context: Context) : ModelL
     }
     
     class AvatarPlaceholderModelLoader(private val context: Context) :
    -    ModelLoader {
    +        ModelLoader {
     
         override fun buildLoadData(model: AvatarPlaceholder, width: Int, height: Int, options: Options): ModelLoader.LoadData? {
             return ModelLoader.LoadData(ObjectKey(model), AvatarPlaceholderDataFetcher(context, model))
    @@ -55,7 +55,7 @@ class AvatarPlaceholderModelLoader(private val context: Context) :
     }
     
     class AvatarPlaceholderDataFetcher(context: Context, private val data: AvatarPlaceholder) :
    -    DataFetcher {
    +        DataFetcher {
     
         private val avatarRenderer = context.singletonEntryPoint().avatarRenderer()
     
    diff --git a/vector/src/main/java/im/vector/app/core/glide/ElementToDecryptOption.kt b/vector/src/main/java/im/vector/app/core/glide/ElementToDecryptOption.kt
    index 1888012450..f4f854406e 100644
    --- a/vector/src/main/java/im/vector/app/core/glide/ElementToDecryptOption.kt
    +++ b/vector/src/main/java/im/vector/app/core/glide/ElementToDecryptOption.kt
    @@ -17,7 +17,7 @@
     package im.vector.app.core.glide
     
     import com.bumptech.glide.load.Option
    -import org.matrix.android.sdk.internal.crypto.attachments.ElementToDecrypt
    +import org.matrix.android.sdk.api.session.crypto.attachments.ElementToDecrypt
     
     const val ElementToDecryptOptionKey = "im.vector.app.core.glide.ElementToDecrypt"
     
    diff --git a/vector/src/main/java/im/vector/app/core/glide/VectorGlideModelLoader.kt b/vector/src/main/java/im/vector/app/core/glide/VectorGlideModelLoader.kt
    index 6b42e3fff8..c53db12b6b 100644
    --- a/vector/src/main/java/im/vector/app/core/glide/VectorGlideModelLoader.kt
    +++ b/vector/src/main/java/im/vector/app/core/glide/VectorGlideModelLoader.kt
    @@ -49,7 +49,7 @@ class VectorGlideModelLoaderFactory(private val context: Context) : ModelLoaderF
     }
     
     class VectorGlideModelLoader(private val context: Context) :
    -    ModelLoader {
    +        ModelLoader {
         override fun handles(model: ImageContentRenderer.Data): Boolean {
             // Always handle
             return true
    @@ -64,7 +64,7 @@ class VectorGlideDataFetcher(context: Context,
                                  private val data: ImageContentRenderer.Data,
                                  private val width: Int,
                                  private val height: Int) :
    -    DataFetcher {
    +        DataFetcher {
     
         private val localFilesHelper = LocalFilesHelper(context)
         private val activeSessionHolder = context.singletonEntryPoint().activeSessionHolder()
    diff --git a/vector/src/main/java/im/vector/app/core/platform/ButtonStateView.kt b/vector/src/main/java/im/vector/app/core/platform/ButtonStateView.kt
    index e7e91dbfd9..7bedeaa4ff 100755
    --- a/vector/src/main/java/im/vector/app/core/platform/ButtonStateView.kt
    +++ b/vector/src/main/java/im/vector/app/core/platform/ButtonStateView.kt
    @@ -29,7 +29,7 @@ import im.vector.app.core.epoxy.onClick
     import im.vector.app.databinding.ViewButtonStateBinding
     
     class ButtonStateView @JvmOverloads constructor(context: Context, attrs: AttributeSet? = null, defStyle: Int = 0) :
    -    FrameLayout(context, attrs, defStyle) {
    +        FrameLayout(context, attrs, defStyle) {
     
         sealed class State {
             object Button : State()
    diff --git a/vector/src/main/java/im/vector/app/core/platform/MaxHeightScrollView.kt b/vector/src/main/java/im/vector/app/core/platform/MaxHeightScrollView.kt
    index 8d4c5d8950..da15d4413d 100644
    --- a/vector/src/main/java/im/vector/app/core/platform/MaxHeightScrollView.kt
    +++ b/vector/src/main/java/im/vector/app/core/platform/MaxHeightScrollView.kt
    @@ -25,7 +25,7 @@ import im.vector.app.R
     private const val DEFAULT_MAX_HEIGHT = 200
     
     class MaxHeightScrollView @JvmOverloads constructor(context: Context, attrs: AttributeSet? = null, defStyle: Int = 0) :
    -    NestedScrollView(context, attrs, defStyle) {
    +        NestedScrollView(context, attrs, defStyle) {
     
         var maxHeight: Int = 0
             set(value) {
    diff --git a/vector/src/main/java/im/vector/app/core/platform/StateView.kt b/vector/src/main/java/im/vector/app/core/platform/StateView.kt
    index b3d42dc38f..a70bf54aa1 100755
    --- a/vector/src/main/java/im/vector/app/core/platform/StateView.kt
    +++ b/vector/src/main/java/im/vector/app/core/platform/StateView.kt
    @@ -27,7 +27,7 @@ import im.vector.app.core.extensions.updateConstraintSet
     import im.vector.app.databinding.ViewStateBinding
     
     class StateView @JvmOverloads constructor(context: Context, attrs: AttributeSet? = null, defStyle: Int = 0) :
    -    FrameLayout(context, attrs, defStyle) {
    +        FrameLayout(context, attrs, defStyle) {
     
         sealed class State {
             object Content : State()
    diff --git a/vector/src/main/java/im/vector/app/core/platform/VectorBaseActivity.kt b/vector/src/main/java/im/vector/app/core/platform/VectorBaseActivity.kt
    index 2c161feb37..febcfc5ef2 100644
    --- a/vector/src/main/java/im/vector/app/core/platform/VectorBaseActivity.kt
    +++ b/vector/src/main/java/im/vector/app/core/platform/VectorBaseActivity.kt
    @@ -46,6 +46,7 @@ import androidx.viewbinding.ViewBinding
     import com.airbnb.mvrx.MavericksView
     import com.bumptech.glide.util.Util
     import com.google.android.material.appbar.MaterialToolbar
    +import com.google.android.material.dialog.MaterialAlertDialogBuilder
     import com.google.android.material.snackbar.Snackbar
     import dagger.hilt.android.EntryPointAccessors
     import im.vector.app.BuildConfig
    @@ -54,7 +55,6 @@ import im.vector.app.core.di.ActiveSessionHolder
     import im.vector.app.core.di.ActivityEntryPoint
     import im.vector.app.core.dialogs.DialogLocker
     import im.vector.app.core.dialogs.UnrecognizedCertificateDialog
    -import im.vector.app.core.extensions.exhaustive
     import im.vector.app.core.extensions.observeEvent
     import im.vector.app.core.extensions.observeNotNull
     import im.vector.app.core.extensions.registerStartForActivityResult
    @@ -87,6 +87,7 @@ import kotlinx.coroutines.flow.launchIn
     import kotlinx.coroutines.flow.onEach
     import org.matrix.android.sdk.api.extensions.tryOrNull
     import org.matrix.android.sdk.api.failure.GlobalError
    +import org.matrix.android.sdk.api.failure.InitialSyncRequestReason
     import reactivecircus.flowbinding.android.view.clicks
     import timber.log.Timber
     import javax.inject.Inject
    @@ -267,7 +268,25 @@ abstract class VectorBaseActivity : AppCompatActivity(), Maver
                 is GlobalError.CertificateError     ->
                     handleCertificateError(globalError)
                 GlobalError.ExpiredAccount          -> Unit // TODO Handle account expiration
    -        }.exhaustive
    +            is GlobalError.InitialSyncRequest   -> handleInitialSyncRequest(globalError)
    +        }
    +    }
    +
    +    private fun handleInitialSyncRequest(initialSyncRequest: GlobalError.InitialSyncRequest) {
    +        MaterialAlertDialogBuilder(this)
    +                .setTitle(R.string.initial_sync_request_title)
    +                .setMessage(
    +                        getString(R.string.initial_sync_request_content, getString(
    +                                when (initialSyncRequest.reason) {
    +                                    InitialSyncRequestReason.IGNORED_USERS_LIST_CHANGE -> R.string.initial_sync_request_reason_unignored_users
    +                                }
    +                        ))
    +                )
    +                .setPositiveButton(R.string.ok) { _, _ ->
    +                    MainActivity.restartApp(this, MainActivityArgs(clearCache = true))
    +                }
    +                .setNegativeButton(R.string.later, null)
    +                .show()
         }
     
         private fun handleCertificateError(certificateError: GlobalError.CertificateError) {
    diff --git a/vector/src/main/java/im/vector/app/core/platform/VectorBaseFragment.kt b/vector/src/main/java/im/vector/app/core/platform/VectorBaseFragment.kt
    index 70b265ff9f..7db35a8e8f 100644
    --- a/vector/src/main/java/im/vector/app/core/platform/VectorBaseFragment.kt
    +++ b/vector/src/main/java/im/vector/app/core/platform/VectorBaseFragment.kt
    @@ -83,8 +83,8 @@ abstract class VectorBaseFragment : Fragment(), MavericksView
          * [ToolbarConfig] instance from host activity
          * */
         protected var toolbar: ToolbarConfig? = null
    -            get() = (activity as? VectorBaseActivity<*>)?.toolbar
    -            private set
    +        get() = (activity as? VectorBaseActivity<*>)?.toolbar
    +        private set
         /* ==========================================================================================
          * View model
          * ========================================================================================== */
    diff --git a/vector/src/main/java/im/vector/app/core/platform/VectorEventViewModel.kt b/vector/src/main/java/im/vector/app/core/platform/VectorEventViewModel.kt
    index 9ab46557a5..2b47412901 100644
    --- a/vector/src/main/java/im/vector/app/core/platform/VectorEventViewModel.kt
    +++ b/vector/src/main/java/im/vector/app/core/platform/VectorEventViewModel.kt
    @@ -26,4 +26,4 @@ interface VectorSharedAction
      * Parent class to handle navigation events, action events, or other any events
      */
     open class VectorSharedActionViewModel(private val store: MutableDataSource = PublishDataSource()) :
    -    ViewModel(), MutableDataSource by store
    +        ViewModel(), MutableDataSource by store
    diff --git a/vector/src/main/java/im/vector/app/core/preference/KeywordPreference.kt b/vector/src/main/java/im/vector/app/core/preference/KeywordPreference.kt
    index b57bb27671..6101f8a597 100644
    --- a/vector/src/main/java/im/vector/app/core/preference/KeywordPreference.kt
    +++ b/vector/src/main/java/im/vector/app/core/preference/KeywordPreference.kt
    @@ -139,7 +139,7 @@ class KeywordPreference : VectorPreference {
                     keyword.contains("/")   -> {
                         context.getString(R.string.settings_notification_keyword_contains_invalid_character, "/")
                     }
    -                else -> null
    +                else                    -> null
                 }
     
                 chipTextInputLayout.isErrorEnabled = errorMessage != null
    diff --git a/vector/src/main/java/im/vector/app/core/preference/PushRulePreference.kt b/vector/src/main/java/im/vector/app/core/preference/PushRulePreference.kt
    index 1a7a79ed8c..78266cf5ee 100644
    --- a/vector/src/main/java/im/vector/app/core/preference/PushRulePreference.kt
    +++ b/vector/src/main/java/im/vector/app/core/preference/PushRulePreference.kt
    @@ -83,6 +83,7 @@ class PushRulePreference : VectorPreference {
                 NotificationIndex.NOISY  -> {
                     radioGroup?.check(R.id.bingPreferenceRadioBingRuleNoisy)
                 }
    +            null                     -> Unit
             }
     
             radioGroup?.setOnCheckedChangeListener { _, checkedId ->
    diff --git a/vector/src/main/java/im/vector/app/core/pushers/PushersManager.kt b/vector/src/main/java/im/vector/app/core/pushers/PushersManager.kt
    index 27d6d05708..b1bb4c7d88 100644
    --- a/vector/src/main/java/im/vector/app/core/pushers/PushersManager.kt
    +++ b/vector/src/main/java/im/vector/app/core/pushers/PushersManager.kt
    @@ -37,7 +37,7 @@ class PushersManager @Inject constructor(
         suspend fun testPush(pushKey: String) {
             val currentSession = activeSessionHolder.getActiveSession()
     
    -        currentSession.testPush(
    +        currentSession.pushersService().testPush(
                     stringProvider.getString(R.string.pusher_http_url),
                     stringProvider.getString(R.string.pusher_app_id),
                     pushKey,
    @@ -47,12 +47,12 @@ class PushersManager @Inject constructor(
     
         fun enqueueRegisterPusherWithFcmKey(pushKey: String): UUID {
             val currentSession = activeSessionHolder.getActiveSession()
    -        return currentSession.enqueueAddHttpPusher(createHttpPusher(pushKey))
    +        return currentSession.pushersService().enqueueAddHttpPusher(createHttpPusher(pushKey))
         }
     
         suspend fun registerPusherWithFcmKey(pushKey: String) {
             val currentSession = activeSessionHolder.getActiveSession()
    -        currentSession.addHttpPusher(createHttpPusher(pushKey))
    +        currentSession.pushersService().addHttpPusher(createHttpPusher(pushKey))
         }
     
         private fun createHttpPusher(pushKey: String) = PushersService.HttpPusher(
    @@ -70,7 +70,7 @@ class PushersManager @Inject constructor(
         suspend fun registerEmailForPush(email: String) {
             val currentSession = activeSessionHolder.getActiveSession()
             val appName = appNameProvider.getAppName()
    -        currentSession.addEmailPusher(
    +        currentSession.pushersService().addEmailPusher(
                     email = email,
                     lang = localeProvider.current().language,
                     emailBranding = appName,
    @@ -81,12 +81,12 @@ class PushersManager @Inject constructor(
     
         suspend fun unregisterEmailPusher(email: String) {
             val currentSession = activeSessionHolder.getSafeActiveSession() ?: return
    -        currentSession.removeEmailPusher(email)
    +        currentSession.pushersService().removeEmailPusher(email)
         }
     
         suspend fun unregisterPusher(pushKey: String) {
             val currentSession = activeSessionHolder.getSafeActiveSession() ?: return
    -        currentSession.removeHttpPusher(pushKey, stringProvider.getString(R.string.pusher_app_id))
    +        currentSession.pushersService().removeHttpPusher(pushKey, stringProvider.getString(R.string.pusher_app_id))
         }
     
         companion object {
    diff --git a/vector/src/main/java/im/vector/app/core/resources/LocaleProvider.kt b/vector/src/main/java/im/vector/app/core/resources/LocaleProvider.kt
    index fdb5f21b61..2f1b46b555 100644
    --- a/vector/src/main/java/im/vector/app/core/resources/LocaleProvider.kt
    +++ b/vector/src/main/java/im/vector/app/core/resources/LocaleProvider.kt
    @@ -32,6 +32,6 @@ class LocaleProvider @Inject constructor(private val resources: Resources) {
     
     fun LocaleProvider.isEnglishSpeaking() = current().language.startsWith("en")
     
    -fun LocaleProvider.getLayoutDirectionFromCurrentLocale() =  TextUtils.getLayoutDirectionFromLocale(current())
    +fun LocaleProvider.getLayoutDirectionFromCurrentLocale() = TextUtils.getLayoutDirectionFromLocale(current())
     
     fun LocaleProvider.isRTL() = getLayoutDirectionFromCurrentLocale() == View.LAYOUT_DIRECTION_RTL
    diff --git a/vector/src/main/java/im/vector/app/core/services/VectorSyncService.kt b/vector/src/main/java/im/vector/app/core/services/VectorSyncService.kt
    index 8621c28d57..5dbea8dcc4 100644
    --- a/vector/src/main/java/im/vector/app/core/services/VectorSyncService.kt
    +++ b/vector/src/main/java/im/vector/app/core/services/VectorSyncService.kt
    @@ -36,7 +36,7 @@ import im.vector.app.core.platform.PendingIntentCompat
     import im.vector.app.features.notifications.NotificationUtils
     import im.vector.app.features.settings.BackgroundSyncMode
     import org.matrix.android.sdk.api.Matrix
    -import org.matrix.android.sdk.internal.session.sync.job.SyncService
    +import org.matrix.android.sdk.api.session.sync.job.SyncService
     import timber.log.Timber
     import javax.inject.Inject
     
    diff --git a/vector/src/main/java/im/vector/app/core/ui/bottomsheet/BottomSheetGenericController.kt b/vector/src/main/java/im/vector/app/core/ui/bottomsheet/BottomSheetGenericController.kt
    index 6e92549809..70faa87645 100644
    --- a/vector/src/main/java/im/vector/app/core/ui/bottomsheet/BottomSheetGenericController.kt
    +++ b/vector/src/main/java/im/vector/app/core/ui/bottomsheet/BottomSheetGenericController.kt
    @@ -21,7 +21,7 @@ import com.airbnb.epoxy.TypedEpoxyController
      * Epoxy controller for generic bottom sheet actions
      */
     abstract class BottomSheetGenericController :
    -    TypedEpoxyController() {
    +        TypedEpoxyController() {
     
         var listener: Listener? = null
     
    diff --git a/vector/src/main/java/im/vector/app/core/ui/views/KeysBackupBanner.kt b/vector/src/main/java/im/vector/app/core/ui/views/KeysBackupBanner.kt
    index 94c1ab6576..58a5666e94 100755
    --- a/vector/src/main/java/im/vector/app/core/ui/views/KeysBackupBanner.kt
    +++ b/vector/src/main/java/im/vector/app/core/ui/views/KeysBackupBanner.kt
    @@ -77,13 +77,10 @@ class KeysBackupBanner @JvmOverloads constructor(
     
         override fun onClick(v: View?) {
             when (state) {
    -            is State.Setup   -> {
    -                delegate?.setupKeysBackup()
    -            }
    +            is State.Setup   -> delegate?.setupKeysBackup()
                 is State.Update,
    -            is State.Recover -> {
    -                delegate?.recoverKeysBackup()
    -            }
    +            is State.Recover -> delegate?.recoverKeysBackup()
    +            else             -> Unit
             }
         }
     
    diff --git a/vector/src/main/java/im/vector/app/core/ui/views/NotificationAreaView.kt b/vector/src/main/java/im/vector/app/core/ui/views/NotificationAreaView.kt
    index 1615e77902..5190bb21a8 100644
    --- a/vector/src/main/java/im/vector/app/core/ui/views/NotificationAreaView.kt
    +++ b/vector/src/main/java/im/vector/app/core/ui/views/NotificationAreaView.kt
    @@ -27,7 +27,6 @@ import androidx.core.text.italic
     import im.vector.app.R
     import im.vector.app.core.epoxy.onClick
     import im.vector.app.core.error.ResourceLimitErrorFormatter
    -import im.vector.app.core.extensions.exhaustive
     import im.vector.app.core.utils.DimensionConverter
     import im.vector.app.databinding.ViewNotificationAreaBinding
     import im.vector.app.features.themes.ThemeUtils
    @@ -77,7 +76,7 @@ class NotificationAreaView @JvmOverloads constructor(
                 is State.UnsupportedAlgorithm       -> renderUnsupportedAlgorithm(newState)
                 is State.Tombstone                  -> renderTombstone()
                 is State.ResourceLimitExceededError -> renderResourceLimitExceededError(newState)
    -        }.exhaustive
    +        }
         }
     
         // PRIVATE METHODS ****************************************************************************************************************************************
    diff --git a/vector/src/main/java/im/vector/app/core/ui/views/PasswordStrengthBar.kt b/vector/src/main/java/im/vector/app/core/ui/views/PasswordStrengthBar.kt
    index a984707bf7..39bfce6975 100644
    --- a/vector/src/main/java/im/vector/app/core/ui/views/PasswordStrengthBar.kt
    +++ b/vector/src/main/java/im/vector/app/core/ui/views/PasswordStrengthBar.kt
    @@ -36,7 +36,7 @@ class PasswordStrengthBar @JvmOverloads constructor(
             context: Context,
             attrs: AttributeSet? = null,
             defStyleAttr: Int = 0) :
    -    LinearLayout(context, attrs, defStyleAttr) {
    +        LinearLayout(context, attrs, defStyleAttr) {
     
         private val views: ViewPasswordStrengthBarBinding
     
    diff --git a/vector/src/main/java/im/vector/app/core/ui/views/PresenceStateImageView.kt b/vector/src/main/java/im/vector/app/core/ui/views/PresenceStateImageView.kt
    index 301f8afdc9..82675e8c11 100644
    --- a/vector/src/main/java/im/vector/app/core/ui/views/PresenceStateImageView.kt
    +++ b/vector/src/main/java/im/vector/app/core/ui/views/PresenceStateImageView.kt
    @@ -49,6 +49,7 @@ class PresenceStateImageView @JvmOverloads constructor(
                     setImageResource(R.drawable.ic_presence_offline)
                     contentDescription = context.getString(R.string.a11y_presence_offline)
                 }
    +            null                     -> Unit
             }
         }
     }
    diff --git a/vector/src/main/java/im/vector/app/core/ui/views/ShieldImageView.kt b/vector/src/main/java/im/vector/app/core/ui/views/ShieldImageView.kt
    index ac0b4408b2..a82a9a30b4 100644
    --- a/vector/src/main/java/im/vector/app/core/ui/views/ShieldImageView.kt
    +++ b/vector/src/main/java/im/vector/app/core/ui/views/ShieldImageView.kt
    @@ -22,7 +22,7 @@ import androidx.annotation.DrawableRes
     import androidx.appcompat.widget.AppCompatImageView
     import androidx.core.view.isVisible
     import im.vector.app.R
    -import org.matrix.android.sdk.api.crypto.RoomEncryptionTrustLevel
    +import org.matrix.android.sdk.api.session.crypto.model.RoomEncryptionTrustLevel
     
     class ShieldImageView @JvmOverloads constructor(
             context: Context,
    @@ -40,21 +40,21 @@ class ShieldImageView @JvmOverloads constructor(
             isVisible = roomEncryptionTrustLevel != null
     
             when (roomEncryptionTrustLevel) {
    -            RoomEncryptionTrustLevel.Default -> {
    +            RoomEncryptionTrustLevel.Default                     -> {
                     contentDescription = context.getString(R.string.a11y_trust_level_default)
                     setImageResource(
                             if (borderLess) R.drawable.ic_shield_black_no_border
                             else R.drawable.ic_shield_black
                     )
                 }
    -            RoomEncryptionTrustLevel.Warning -> {
    +            RoomEncryptionTrustLevel.Warning                     -> {
                     contentDescription = context.getString(R.string.a11y_trust_level_warning)
                     setImageResource(
                             if (borderLess) R.drawable.ic_shield_warning_no_border
                             else R.drawable.ic_shield_warning
                     )
                 }
    -            RoomEncryptionTrustLevel.Trusted -> {
    +            RoomEncryptionTrustLevel.Trusted                     -> {
                     contentDescription = context.getString(R.string.a11y_trust_level_trusted)
                     setImageResource(
                             if (borderLess) R.drawable.ic_shield_trusted_no_border
    @@ -65,6 +65,7 @@ class ShieldImageView @JvmOverloads constructor(
                     contentDescription = context.getString(R.string.a11y_trust_level_trusted)
                     setImageResource(R.drawable.ic_warning_badge)
                 }
    +            null                                                 -> Unit
             }
         }
     }
    @@ -72,9 +73,9 @@ class ShieldImageView @JvmOverloads constructor(
     @DrawableRes
     fun RoomEncryptionTrustLevel.toDrawableRes(): Int {
         return when (this) {
    -        RoomEncryptionTrustLevel.Default -> R.drawable.ic_shield_black
    -        RoomEncryptionTrustLevel.Warning -> R.drawable.ic_shield_warning
    -        RoomEncryptionTrustLevel.Trusted -> R.drawable.ic_shield_trusted
    +        RoomEncryptionTrustLevel.Default                     -> R.drawable.ic_shield_black
    +        RoomEncryptionTrustLevel.Warning                     -> R.drawable.ic_shield_warning
    +        RoomEncryptionTrustLevel.Trusted                     -> R.drawable.ic_shield_trusted
             RoomEncryptionTrustLevel.E2EWithUnsupportedAlgorithm -> R.drawable.ic_warning_badge
         }
     }
    diff --git a/vector/src/main/java/im/vector/app/core/ui/views/TypingMessageDotsView.kt b/vector/src/main/java/im/vector/app/core/ui/views/TypingMessageDotsView.kt
    index bc06254b0d..2e9945c8d7 100644
    --- a/vector/src/main/java/im/vector/app/core/ui/views/TypingMessageDotsView.kt
    +++ b/vector/src/main/java/im/vector/app/core/ui/views/TypingMessageDotsView.kt
    @@ -30,7 +30,7 @@ import androidx.core.view.setMargins
     import im.vector.app.R
     
     class TypingMessageDotsView(context: Context, attrs: AttributeSet) :
    -    LinearLayout(context, attrs) {
    +        LinearLayout(context, attrs) {
     
         companion object {
             const val DEFAULT_CIRCLE_DURATION = 1000L
    diff --git a/vector/src/main/java/im/vector/app/core/utils/LiveData.kt b/vector/src/main/java/im/vector/app/core/utils/LiveData.kt
    new file mode 100644
    index 0000000000..922fd46235
    --- /dev/null
    +++ b/vector/src/main/java/im/vector/app/core/utils/LiveData.kt
    @@ -0,0 +1,42 @@
    +/*
    + * Copyright (c) 2022 New Vector Ltd
    + *
    + * Licensed under the Apache License, Version 2.0 (the "License");
    + * you may not use this file except in compliance with the License.
    + * You may obtain a copy of the License at
    + *
    + *     http://www.apache.org/licenses/LICENSE-2.0
    + *
    + * Unless required by applicable law or agreed to in writing, software
    + * distributed under the License is distributed on an "AS IS" BASIS,
    + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    + * See the License for the specific language governing permissions and
    + * limitations under the License.
    + */
    +
    +package im.vector.app.core.utils
    +
    +import androidx.lifecycle.LiveData
    +import androidx.lifecycle.MediatorLiveData
    +
    +fun  combineLatest(source1: LiveData, source2: LiveData, mapper: (T1, T2) -> R): LiveData {
    +    val combined = MediatorLiveData()
    +    var source1Result: T1? = null
    +    var source2Result: T2? = null
    +
    +    fun notify() {
    +        if (source1Result != null && source2Result != null) {
    +            combined.value = mapper(source1Result!!, source2Result!!)
    +        }
    +    }
    +
    +    combined.addSource(source1) {
    +        source1Result = it
    +        notify()
    +    }
    +    combined.addSource(source2) {
    +        source2Result = it
    +        notify()
    +    }
    +    return combined
    +}
    diff --git a/vector/src/main/java/im/vector/app/features/MainActivity.kt b/vector/src/main/java/im/vector/app/features/MainActivity.kt
    index 33b735551c..121edd4216 100644
    --- a/vector/src/main/java/im/vector/app/features/MainActivity.kt
    +++ b/vector/src/main/java/im/vector/app/features/MainActivity.kt
    @@ -160,7 +160,7 @@ class MainActivity : VectorBaseActivity(), UnlockedActivity
                 args.clearCredentials     -> {
                     lifecycleScope.launch {
                         try {
    -                        session.signOut(!args.isUserLoggedOut)
    +                        session.signOutService().signOut(!args.isUserLoggedOut)
                         } catch (failure: Throwable) {
                             displayError(failure)
                             return@launch
    @@ -241,7 +241,7 @@ class MainActivity : VectorBaseActivity(), UnlockedActivity
                     // We have a session.
                     // Check it can be opened
                     if (sessionHolder.getActiveSession().isOpenable) {
    -                    HomeActivity.newIntent(this)
    +                    HomeActivity.newIntent(this, existingSession = true)
                     } else {
                         // The token is still invalid
                         navigator.softLogout(this)
    diff --git a/vector/src/main/java/im/vector/app/features/VectorFeatures.kt b/vector/src/main/java/im/vector/app/features/VectorFeatures.kt
    index a19b3d9026..9d54475e8c 100644
    --- a/vector/src/main/java/im/vector/app/features/VectorFeatures.kt
    +++ b/vector/src/main/java/im/vector/app/features/VectorFeatures.kt
    @@ -25,6 +25,9 @@ interface VectorFeatures {
         fun isOnboardingSplashCarouselEnabled(): Boolean
         fun isOnboardingUseCaseEnabled(): Boolean
         fun isOnboardingPersonalizeEnabled(): Boolean
    +    fun isOnboardingCombinedRegisterEnabled(): Boolean
    +    fun isLiveLocationEnabled(): Boolean
    +    fun isScreenSharingEnabled(): Boolean
     
         enum class OnboardingVariant {
             LEGACY,
    @@ -39,4 +42,7 @@ class DefaultVectorFeatures : VectorFeatures {
         override fun isOnboardingSplashCarouselEnabled() = true
         override fun isOnboardingUseCaseEnabled() = true
         override fun isOnboardingPersonalizeEnabled() = false
    +    override fun isOnboardingCombinedRegisterEnabled() = false
    +    override fun isLiveLocationEnabled(): Boolean = false
    +    override fun isScreenSharingEnabled(): Boolean = false
     }
    diff --git a/vector/src/main/java/im/vector/app/features/analytics/accountdata/AnalyticsAccountDataViewModel.kt b/vector/src/main/java/im/vector/app/features/analytics/accountdata/AnalyticsAccountDataViewModel.kt
    index 3b92e7c4de..221a9d8843 100644
    --- a/vector/src/main/java/im/vector/app/features/analytics/accountdata/AnalyticsAccountDataViewModel.kt
    +++ b/vector/src/main/java/im/vector/app/features/analytics/accountdata/AnalyticsAccountDataViewModel.kt
    @@ -66,7 +66,7 @@ class AnalyticsAccountDataViewModel @AssistedInject constructor(
     
         private fun observeInitSync() {
             combine(
    -                session.getSyncStatusLive().asFlow(),
    +                session.syncStatusService().getSyncStatusLive().asFlow(),
                     analytics.getUserConsent(),
                     analytics.getAnalyticsId()
             ) { status, userConsent, analyticsId ->
    diff --git a/vector/src/main/java/im/vector/app/features/analytics/plan/JoinedRoom.kt b/vector/src/main/java/im/vector/app/features/analytics/plan/JoinedRoom.kt
    index ccbe025a8d..0011d0e144 100644
    --- a/vector/src/main/java/im/vector/app/features/analytics/plan/JoinedRoom.kt
    +++ b/vector/src/main/java/im/vector/app/features/analytics/plan/JoinedRoom.kt
    @@ -49,6 +49,16 @@ data class JoinedRoom(
              */
             Invite,
     
    +        /**
    +         * Room joined via space explore
    +         */
    +        MobileExploreRooms,
    +
    +        /**
    +         * Room joined via link
    +         */
    +        MobilePermalink,
    +
             /**
              * Room joined via a push/desktop notification.
              */
    diff --git a/vector/src/main/java/im/vector/app/features/analytics/plan/Signup.kt b/vector/src/main/java/im/vector/app/features/analytics/plan/Signup.kt
    new file mode 100644
    index 0000000000..328340fd2f
    --- /dev/null
    +++ b/vector/src/main/java/im/vector/app/features/analytics/plan/Signup.kt
    @@ -0,0 +1,84 @@
    +/*
    + * Copyright (c) 2021 New Vector Ltd
    + *
    + * Licensed under the Apache License, Version 2.0 (the "License");
    + * you may not use this file except in compliance with the License.
    + * You may obtain a copy of the License at
    + *
    + *     http://www.apache.org/licenses/LICENSE-2.0
    + *
    + * Unless required by applicable law or agreed to in writing, software
    + * distributed under the License is distributed on an "AS IS" BASIS,
    + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    + * See the License for the specific language governing permissions and
    + * limitations under the License.
    + */
    +
    +package im.vector.app.features.analytics.plan
    +
    +import im.vector.app.features.analytics.itf.VectorAnalyticsEvent
    +
    +// GENERATED FILE, DO NOT EDIT. FOR MORE INFORMATION VISIT
    +// https://github.com/matrix-org/matrix-analytics-events/
    +
    +/**
    + * Triggered once onboarding has completed, but only if the user registered a
    + * new account.
    + */
    +data class Signup(
    +        /**
    +         * The type of authentication that was used to sign up.
    +         */
    +        val authenticationType: AuthenticationType,
    +) : VectorAnalyticsEvent {
    +
    +    enum class AuthenticationType {
    +        /**
    +         * Social login using Apple.
    +         */
    +        Apple,
    +
    +        /**
    +         * Social login using Facebook.
    +         */
    +        Facebook,
    +
    +        /**
    +         * Social login using GitHub.
    +         */
    +        GitHub,
    +
    +        /**
    +         * Social login using GitLab.
    +         */
    +        GitLab,
    +
    +        /**
    +         * Social login using Google.
    +         */
    +        Google,
    +
    +        /**
    +         * Registration using some other mechanism such as fallback.
    +         */
    +        Other,
    +
    +        /**
    +         * Registration with a username and password.
    +         */
    +        Password,
    +
    +        /**
    +         * Registration using another SSO provider.
    +         */
    +        SSO,
    +    }
    +
    +    override fun getName() = "Signup"
    +
    +    override fun getProperties(): Map? {
    +        return mutableMapOf().apply {
    +            put("authenticationType", authenticationType.name)
    +        }.takeIf { it.isNotEmpty() }
    +    }
    +}
    diff --git a/vector/src/main/java/im/vector/app/features/analytics/plan/ViewRoom.kt b/vector/src/main/java/im/vector/app/features/analytics/plan/ViewRoom.kt
    index a73ca5a9b3..e9bef6f1d3 100644
    --- a/vector/src/main/java/im/vector/app/features/analytics/plan/ViewRoom.kt
    +++ b/vector/src/main/java/im/vector/app/features/analytics/plan/ViewRoom.kt
    @@ -63,6 +63,74 @@ data class ViewRoom(
              */
             MessageUser,
     
    +        /**
    +         * Room accessed via space explore
    +         */
    +        MobileExploreRooms,
    +
    +        /**
    +         * Room switched due to user interacting with a file search result.
    +         */
    +        MobileFileSearch,
    +
    +        /**
    +         * Room accessed via interacting with the incall screen.
    +         */
    +        MobileInCall,
    +
    +        /**
    +         * Room accessed during external sharing
    +         */
    +        MobileLinkShare,
    +
    +        /**
    +         * Room accessed via link
    +         */
    +        MobilePermalink,
    +
    +        /**
    +         * Room accessed via interacting with direct chat item in the room
    +         * contact detail screen.
    +         */
    +        MobileRoomMemberDetail,
    +
    +        /**
    +         * Room accessed via preview
    +         */
    +        MobileRoomPreview,
    +
    +        /**
    +         * Room switched due to user interacting with a room search result.
    +         */
    +        MobileRoomSearch,
    +
    +        /**
    +         * Room accessed via interacting with direct chat item in the search
    +         * contact detail screen.
    +         */
    +        MobileSearchContactDetail,
    +
    +        /**
    +         * Room accessed via interacting with direct chat item in the space
    +         * contact detail screen.
    +         */
    +        MobileSpaceMemberDetail,
    +
    +        /**
    +         * Room accessed via space members list
    +         */
    +        MobileSpaceMembers,
    +
    +        /**
    +         * Space accessed via interacting with the space menu.
    +         */
    +        MobileSpaceMenu,
    +
    +        /**
    +         * Space accessed via interacting with a space settings menu item.
    +         */
    +        MobileSpaceSettings,
    +
             /**
              * Room accessed via a push/desktop notification.
              */
    diff --git a/vector/src/main/java/im/vector/app/features/analytics/ui/consent/AnalyticsConsentViewModel.kt b/vector/src/main/java/im/vector/app/features/analytics/ui/consent/AnalyticsConsentViewModel.kt
    index 2c7a8ac9bc..a570b31452 100644
    --- a/vector/src/main/java/im/vector/app/features/analytics/ui/consent/AnalyticsConsentViewModel.kt
    +++ b/vector/src/main/java/im/vector/app/features/analytics/ui/consent/AnalyticsConsentViewModel.kt
    @@ -22,7 +22,6 @@ import dagger.assisted.AssistedFactory
     import dagger.assisted.AssistedInject
     import im.vector.app.core.di.MavericksAssistedViewModelFactory
     import im.vector.app.core.di.hiltMavericksViewModelFactory
    -import im.vector.app.core.extensions.exhaustive
     import im.vector.app.core.platform.VectorViewModel
     import im.vector.app.features.analytics.VectorAnalytics
     import kotlinx.coroutines.launch
    @@ -55,7 +54,7 @@ class AnalyticsConsentViewModel @AssistedInject constructor(
         override fun handle(action: AnalyticsConsentViewActions) {
             when (action) {
                 is AnalyticsConsentViewActions.SetUserConsent -> handleSetUserConsent(action)
    -        }.exhaustive
    +        }
         }
     
         private fun handleSetUserConsent(action: AnalyticsConsentViewActions.SetUserConsent) {
    diff --git a/vector/src/main/java/im/vector/app/features/analytics/ui/consent/AnalyticsOptInActivity.kt b/vector/src/main/java/im/vector/app/features/analytics/ui/consent/AnalyticsOptInActivity.kt
    index c84031d2fd..c11cf582d3 100644
    --- a/vector/src/main/java/im/vector/app/features/analytics/ui/consent/AnalyticsOptInActivity.kt
    +++ b/vector/src/main/java/im/vector/app/features/analytics/ui/consent/AnalyticsOptInActivity.kt
    @@ -19,7 +19,6 @@ package im.vector.app.features.analytics.ui.consent
     import com.airbnb.mvrx.viewModel
     import dagger.hilt.android.AndroidEntryPoint
     import im.vector.app.core.extensions.addFragment
    -import im.vector.app.core.extensions.exhaustive
     import im.vector.app.core.platform.ScreenOrientationLocker
     import im.vector.app.core.platform.VectorBaseActivity
     import im.vector.app.databinding.ActivitySimpleBinding
    @@ -48,7 +47,7 @@ class AnalyticsOptInActivity : VectorBaseActivity() {
             viewModel.observeViewEvents {
                 when (it) {
                     AnalyticsOptInViewEvents.OnDataSaved -> finish()
    -            }.exhaustive
    +            }
             }
         }
     }
    diff --git a/vector/src/main/java/im/vector/app/features/analytics/ui/consent/AnalyticsOptInFragment.kt b/vector/src/main/java/im/vector/app/features/analytics/ui/consent/AnalyticsOptInFragment.kt
    index f112ba4659..320386aebd 100644
    --- a/vector/src/main/java/im/vector/app/features/analytics/ui/consent/AnalyticsOptInFragment.kt
    +++ b/vector/src/main/java/im/vector/app/features/analytics/ui/consent/AnalyticsOptInFragment.kt
    @@ -31,7 +31,7 @@ import im.vector.app.databinding.FragmentAnalyticsOptinBinding
     import javax.inject.Inject
     
     class AnalyticsOptInFragment @Inject constructor() :
    -    VectorBaseFragment(),
    +        VectorBaseFragment(),
             OnBackPressed {
     
         // Share the view model with the Activity so that the Activity
    diff --git a/vector/src/main/java/im/vector/app/features/attachments/AttachmentTypeSelectorView.kt b/vector/src/main/java/im/vector/app/features/attachments/AttachmentTypeSelectorView.kt
    index 7fcbb6bae6..98440632d8 100644
    --- a/vector/src/main/java/im/vector/app/features/attachments/AttachmentTypeSelectorView.kt
    +++ b/vector/src/main/java/im/vector/app/features/attachments/AttachmentTypeSelectorView.kt
    @@ -41,6 +41,7 @@ import im.vector.app.core.utils.PERMISSIONS_FOR_FOREGROUND_LOCATION_SHARING
     import im.vector.app.core.utils.PERMISSIONS_FOR_PICKING_CONTACT
     import im.vector.app.core.utils.PERMISSIONS_FOR_TAKING_PHOTO
     import im.vector.app.databinding.ViewAttachmentTypeSelectorBinding
    +import im.vector.app.features.attachments.AttachmentTypeSelectorView.Callback
     import kotlin.math.max
     
     private const val ANIMATION_DURATION = 250
    diff --git a/vector/src/main/java/im/vector/app/features/attachments/preview/AttachmentsPreviewViewModel.kt b/vector/src/main/java/im/vector/app/features/attachments/preview/AttachmentsPreviewViewModel.kt
    index 0a0e700ce9..81f0994899 100644
    --- a/vector/src/main/java/im/vector/app/features/attachments/preview/AttachmentsPreviewViewModel.kt
    +++ b/vector/src/main/java/im/vector/app/features/attachments/preview/AttachmentsPreviewViewModel.kt
    @@ -17,18 +17,17 @@
     
     package im.vector.app.features.attachments.preview
     
    -import im.vector.app.core.extensions.exhaustive
     import im.vector.app.core.platform.VectorViewModel
     
     class AttachmentsPreviewViewModel(initialState: AttachmentsPreviewViewState) :
    -    VectorViewModel(initialState) {
    +        VectorViewModel(initialState) {
     
         override fun handle(action: AttachmentsPreviewAction) {
             when (action) {
                 is AttachmentsPreviewAction.SetCurrentAttachment          -> handleSetCurrentAttachment(action)
                 is AttachmentsPreviewAction.UpdatePathOfCurrentAttachment -> handleUpdatePathOfCurrentAttachment(action)
                 AttachmentsPreviewAction.RemoveCurrentAttachment          -> handleRemoveCurrentAttachment()
    -        }.exhaustive
    +        }
         }
     
         private fun handleRemoveCurrentAttachment() = withState {
    diff --git a/vector/src/main/java/im/vector/app/features/auth/PromptFragment.kt b/vector/src/main/java/im/vector/app/features/auth/PromptFragment.kt
    index 501af00ad2..bd44a5b9cd 100644
    --- a/vector/src/main/java/im/vector/app/features/auth/PromptFragment.kt
    +++ b/vector/src/main/java/im/vector/app/features/auth/PromptFragment.kt
    @@ -65,7 +65,7 @@ class PromptFragment : VectorBaseFragment() {
     
         override fun invalidate() = withState(viewModel) {
             when (it.flowType) {
    -            LoginFlowTypes.SSO -> {
    +            LoginFlowTypes.SSO      -> {
                     views.passwordFieldTil.isVisible = false
                     views.reAuthConfirmButton.text = getString(R.string.auth_login_sso)
                 }
    diff --git a/vector/src/main/java/im/vector/app/features/auth/ReAuthActivity.kt b/vector/src/main/java/im/vector/app/features/auth/ReAuthActivity.kt
    index 78c6cbb021..f92ec8ea9e 100644
    --- a/vector/src/main/java/im/vector/app/features/auth/ReAuthActivity.kt
    +++ b/vector/src/main/java/im/vector/app/features/auth/ReAuthActivity.kt
    @@ -86,10 +86,10 @@ class ReAuthActivity : SimpleFragmentActivity() {
     
             sharedViewModel.observeViewEvents {
                 when (it) {
    -                is ReAuthEvents.OpenSsoURl -> {
    +                is ReAuthEvents.OpenSsoURl            -> {
                         openInCustomTab(it.url)
                     }
    -                ReAuthEvents.Dismiss -> {
    +                ReAuthEvents.Dismiss                  -> {
                         setResult(RESULT_CANCELED)
                         finish()
                     }
    @@ -205,7 +205,7 @@ class ReAuthActivity : SimpleFragmentActivity() {
                     LoginFlowTypes.PASSWORD -> {
                         LoginFlowTypes.PASSWORD
                     }
    -                LoginFlowTypes.SSO -> {
    +                LoginFlowTypes.SSO      -> {
                         LoginFlowTypes.SSO
                     }
                     else                    -> {
    diff --git a/vector/src/main/java/im/vector/app/features/auth/ReAuthViewModel.kt b/vector/src/main/java/im/vector/app/features/auth/ReAuthViewModel.kt
    index 830fd4e79d..0bd26870e5 100644
    --- a/vector/src/main/java/im/vector/app/features/auth/ReAuthViewModel.kt
    +++ b/vector/src/main/java/im/vector/app/features/auth/ReAuthViewModel.kt
    @@ -25,7 +25,7 @@ import im.vector.app.core.di.hiltMavericksViewModelFactory
     import im.vector.app.core.platform.VectorViewModel
     import org.matrix.android.sdk.api.auth.data.LoginFlowTypes
     import org.matrix.android.sdk.api.session.Session
    -import org.matrix.android.sdk.internal.crypto.crosssigning.toBase64NoPadding
    +import org.matrix.android.sdk.api.util.toBase64NoPadding
     import java.io.ByteArrayOutputStream
     
     class ReAuthViewModel @AssistedInject constructor(
    @@ -42,7 +42,7 @@ class ReAuthViewModel @AssistedInject constructor(
     
         override fun handle(action: ReAuthActions) = withState { state ->
             when (action) {
    -            ReAuthActions.StartSSOFallback -> {
    +            ReAuthActions.StartSSOFallback   -> {
                     if (state.flowType == LoginFlowTypes.SSO) {
                         setState { copy(ssoFallbackPageWasShown = true) }
                         val ssoURL = session.getUiaSsoFallbackUrl(initialState.session ?: "")
    @@ -55,10 +55,10 @@ class ReAuthViewModel @AssistedInject constructor(
                 ReAuthActions.FallBackPageClosed -> {
                     // Should we do something here?
                 }
    -            is ReAuthActions.ReAuthWithPass -> {
    +            is ReAuthActions.ReAuthWithPass  -> {
                     val safeForIntentCypher = ByteArrayOutputStream().also {
                         it.use {
    -                        session.securelyStoreObject(action.password, initialState.resultKeyStoreAlias, it)
    +                        session.secureStorageService().securelyStoreObject(action.password, initialState.resultKeyStoreAlias, it)
                         }
                     }.toByteArray().toBase64NoPadding()
                     _viewEvents.post(ReAuthEvents.PasswordFinishSuccess(safeForIntentCypher))
    diff --git a/vector/src/main/java/im/vector/app/features/autocomplete/group/AutocompleteGroupPresenter.kt b/vector/src/main/java/im/vector/app/features/autocomplete/group/AutocompleteGroupPresenter.kt
    index a3c276c977..fc7479eb21 100644
    --- a/vector/src/main/java/im/vector/app/features/autocomplete/group/AutocompleteGroupPresenter.kt
    +++ b/vector/src/main/java/im/vector/app/features/autocomplete/group/AutocompleteGroupPresenter.kt
    @@ -55,7 +55,7 @@ class AutocompleteGroupPresenter @Inject constructor(context: Context,
                     QueryStringValue.Contains(query.toString(), QueryStringValue.Case.INSENSITIVE)
                 }
             }
    -        val groups = session.getGroupSummaries(queryParams)
    +        val groups = session.groupService().getGroupSummaries(queryParams)
                     .asSequence()
                     .sortedBy { it.displayName }
             controller.setData(groups.toList())
    diff --git a/vector/src/main/java/im/vector/app/features/autocomplete/member/AutocompleteMemberPresenter.kt b/vector/src/main/java/im/vector/app/features/autocomplete/member/AutocompleteMemberPresenter.kt
    index ce3b9c6a7e..54ab3f84ee 100644
    --- a/vector/src/main/java/im/vector/app/features/autocomplete/member/AutocompleteMemberPresenter.kt
    +++ b/vector/src/main/java/im/vector/app/features/autocomplete/member/AutocompleteMemberPresenter.kt
    @@ -24,10 +24,11 @@ import dagger.assisted.AssistedInject
     import im.vector.app.R
     import im.vector.app.features.autocomplete.AutocompleteClickListener
     import im.vector.app.features.autocomplete.RecyclerViewPresenter
    -import org.matrix.android.sdk.api.pushrules.SenderNotificationPermissionCondition
     import org.matrix.android.sdk.api.query.QueryStringValue
     import org.matrix.android.sdk.api.session.Session
     import org.matrix.android.sdk.api.session.events.model.Event
    +import org.matrix.android.sdk.api.session.getRoom
    +import org.matrix.android.sdk.api.session.pushrules.SenderNotificationPermissionCondition
     import org.matrix.android.sdk.api.session.room.members.RoomMemberQueryParams
     import org.matrix.android.sdk.api.session.room.members.roomMemberQueryParams
     import org.matrix.android.sdk.api.session.room.model.Membership
    @@ -147,7 +148,7 @@ class AutocompleteMemberPresenter @AssistedInject constructor(context: Context,
                             AutocompleteMemberItem.Everyone(it)
                         }
     
    -    private fun canNotifyEveryone() = session.resolveSenderNotificationPermissionCondition(
    +    private fun canNotifyEveryone() = session.pushRuleService().resolveSenderNotificationPermissionCondition(
                 Event(
                         senderId = session.myUserId,
                         roomId = roomId
    diff --git a/vector/src/main/java/im/vector/app/features/autocomplete/room/AutocompleteRoomPresenter.kt b/vector/src/main/java/im/vector/app/features/autocomplete/room/AutocompleteRoomPresenter.kt
    index 99f0af7184..d9310e295d 100644
    --- a/vector/src/main/java/im/vector/app/features/autocomplete/room/AutocompleteRoomPresenter.kt
    +++ b/vector/src/main/java/im/vector/app/features/autocomplete/room/AutocompleteRoomPresenter.kt
    @@ -51,7 +51,7 @@ class AutocompleteRoomPresenter @Inject constructor(context: Context,
                     QueryStringValue.Contains(query.toString(), QueryStringValue.Case.INSENSITIVE)
                 }
             }
    -        val rooms = session.getRoomSummaries(queryParams)
    +        val rooms = session.roomService().getRoomSummaries(queryParams)
                     .asSequence()
                     .sortedBy { it.displayName }
             controller.setData(rooms.toList())
    diff --git a/vector/src/main/java/im/vector/app/features/badge/BadgeProxy.kt b/vector/src/main/java/im/vector/app/features/badge/BadgeProxy.kt
    index 3df33b0c9b..fb597d1ef9 100644
    --- a/vector/src/main/java/im/vector/app/features/badge/BadgeProxy.kt
    +++ b/vector/src/main/java/im/vector/app/features/badge/BadgeProxy.kt
    @@ -90,7 +90,7 @@ object BadgeProxy {
                     }
                 }
             }
    -        */
    +         */
         }
     
         /**
    @@ -124,6 +124,6 @@ object BadgeProxy {
                 Timber.v("## updateBadgeCount(): badge update count=$unreadRoomsCount")
                 updateBadgeCount(aContext, unreadRoomsCount)
             }
    -        */
    +         */
         }
     }
    diff --git a/vector/src/main/java/im/vector/app/features/call/CallControlsBottomSheet.kt b/vector/src/main/java/im/vector/app/features/call/CallControlsBottomSheet.kt
    index e38b53c858..11a43bcd0c 100644
    --- a/vector/src/main/java/im/vector/app/features/call/CallControlsBottomSheet.kt
    +++ b/vector/src/main/java/im/vector/app/features/call/CallControlsBottomSheet.kt
    @@ -27,6 +27,8 @@ import dagger.hilt.android.AndroidEntryPoint
     import im.vector.app.R
     import im.vector.app.core.platform.VectorBaseBottomSheetDialogFragment
     import im.vector.app.databinding.BottomSheetCallControlsBinding
    +import im.vector.app.features.VectorFeatures
    +import javax.inject.Inject
     
     @AndroidEntryPoint
     class CallControlsBottomSheet : VectorBaseBottomSheetDialogFragment() {
    @@ -34,6 +36,8 @@ class CallControlsBottomSheet : VectorBaseBottomSheetDialogFragment Unit
             }
         }
     
    diff --git a/vector/src/main/java/im/vector/app/features/call/VectorCallActivity.kt b/vector/src/main/java/im/vector/app/features/call/VectorCallActivity.kt
    index 23c7b79914..ea9adcde85 100644
    --- a/vector/src/main/java/im/vector/app/features/call/VectorCallActivity.kt
    +++ b/vector/src/main/java/im/vector/app/features/call/VectorCallActivity.kt
    @@ -24,6 +24,7 @@ import android.content.Intent
     import android.content.Intent.FLAG_ACTIVITY_CLEAR_TOP
     import android.content.res.Configuration
     import android.graphics.Color
    +import android.media.projection.MediaProjectionManager
     import android.os.Build
     import android.os.Bundle
     import android.os.Parcelable
    @@ -56,6 +57,8 @@ import im.vector.app.features.call.dialpad.CallDialPadBottomSheet
     import im.vector.app.features.call.dialpad.DialPadFragment
     import im.vector.app.features.call.transfer.CallTransferActivity
     import im.vector.app.features.call.utils.EglUtils
    +import im.vector.app.features.call.webrtc.ScreenCaptureService
    +import im.vector.app.features.call.webrtc.ScreenCaptureServiceConnection
     import im.vector.app.features.call.webrtc.WebRtcCall
     import im.vector.app.features.call.webrtc.WebRtcCallManager
     import im.vector.app.features.displayname.getBestName
    @@ -94,6 +97,7 @@ class VectorCallActivity : VectorBaseActivity(), CallContro
     
         @Inject lateinit var callManager: WebRtcCallManager
         @Inject lateinit var avatarRenderer: AvatarRenderer
    +    @Inject lateinit var screenCaptureServiceConnection: ScreenCaptureServiceConnection
     
         private val callViewModel: VectorCallViewModel by viewModel()
     
    @@ -512,21 +516,22 @@ class VectorCallActivity : VectorBaseActivity(), CallContro
         private fun handleViewEvents(event: VectorCallViewEvents?) {
             Timber.tag(loggerTag.value).v("handleViewEvents $event")
             when (event) {
    -            is VectorCallViewEvents.ConnectionTimeout      -> {
    +            is VectorCallViewEvents.ConnectionTimeout                 -> {
                     onErrorTimoutConnect(event.turn)
                 }
    -            is VectorCallViewEvents.ShowDialPad            -> {
    +            is VectorCallViewEvents.ShowDialPad                       -> {
                     CallDialPadBottomSheet.newInstance(false).apply {
                         callback = dialPadCallback
                     }.show(supportFragmentManager, FRAGMENT_DIAL_PAD_TAG)
                 }
    -            is VectorCallViewEvents.ShowCallTransferScreen -> {
    +            is VectorCallViewEvents.ShowCallTransferScreen            -> {
                     val callId = withState(callViewModel) { it.callId }
                     navigator.openCallTransfer(this, callTransferActivityResultLauncher, callId)
                 }
    -            is VectorCallViewEvents.FailToTransfer         -> showSnackbar(getString(R.string.call_transfer_failure))
    -            null                                           -> {
    -            }
    +            is VectorCallViewEvents.FailToTransfer                    -> showSnackbar(getString(R.string.call_transfer_failure))
    +            is VectorCallViewEvents.ShowScreenSharingPermissionDialog -> handleShowScreenSharingPermissionDialog()
    +            is VectorCallViewEvents.StopScreenSharingService          -> handleStopScreenSharingService()
    +            else                                                      -> Unit
             }
         }
     
    @@ -629,6 +634,32 @@ class VectorCallActivity : VectorBaseActivity(), CallContro
             }
         }
     
    +    private val screenSharingPermissionActivityResultLauncher = registerStartForActivityResult { activityResult ->
    +        if (activityResult.resultCode == Activity.RESULT_OK) {
    +            callViewModel.handle(VectorCallViewActions.StartScreenSharing)
    +            // We need to start a foreground service with a sticky notification during screen sharing
    +            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
    +                ContextCompat.startForegroundService(
    +                        this,
    +                        Intent(this, ScreenCaptureService::class.java)
    +                )
    +                screenCaptureServiceConnection.bind()
    +            }
    +        }
    +    }
    +
    +    private fun handleShowScreenSharingPermissionDialog() {
    +        getSystemService()?.let {
    +            navigator.openScreenSharingPermissionDialog(it.createScreenCaptureIntent(), screenSharingPermissionActivityResultLauncher)
    +        }
    +    }
    +
    +    private fun handleStopScreenSharingService() {
    +        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
    +            screenCaptureServiceConnection.stopScreenCapturing()
    +        }
    +    }
    +
         companion object {
             private const val EXTRA_MODE = "EXTRA_MODE"
             private const val FRAGMENT_DIAL_PAD_TAG = "FRAGMENT_DIAL_PAD_TAG"
    diff --git a/vector/src/main/java/im/vector/app/features/call/VectorCallViewActions.kt b/vector/src/main/java/im/vector/app/features/call/VectorCallViewActions.kt
    index d1ed961814..c84f733b9a 100644
    --- a/vector/src/main/java/im/vector/app/features/call/VectorCallViewActions.kt
    +++ b/vector/src/main/java/im/vector/app/features/call/VectorCallViewActions.kt
    @@ -40,4 +40,6 @@ sealed class VectorCallViewActions : VectorViewModelAction {
         object CallTransferSelectionCancelled : VectorCallViewActions()
         data class CallTransferSelectionResult(val callTransferResult: CallTransferResult) : VectorCallViewActions()
         object TransferCall : VectorCallViewActions()
    +    object ToggleScreenSharing : VectorCallViewActions()
    +    object StartScreenSharing : VectorCallViewActions()
     }
    diff --git a/vector/src/main/java/im/vector/app/features/call/VectorCallViewEvents.kt b/vector/src/main/java/im/vector/app/features/call/VectorCallViewEvents.kt
    index 7c29d7eea3..52510f6f8b 100644
    --- a/vector/src/main/java/im/vector/app/features/call/VectorCallViewEvents.kt
    +++ b/vector/src/main/java/im/vector/app/features/call/VectorCallViewEvents.kt
    @@ -27,10 +27,10 @@ sealed class VectorCallViewEvents : VectorViewEvents {
                 val available: Set,
                 val current: CallAudioManager.Device
         ) : VectorCallViewEvents()
    +
         object ShowDialPad : VectorCallViewEvents()
         object ShowCallTransferScreen : VectorCallViewEvents()
         object FailToTransfer : VectorCallViewEvents()
    -//    data class CallAnswered(val content: CallAnswerContent) : VectorCallViewEvents()
    -//    data class CallHangup(val content: CallHangupContent) : VectorCallViewEvents()
    -//    object CallAccepted : VectorCallViewEvents()
    +    object ShowScreenSharingPermissionDialog : VectorCallViewEvents()
    +    object StopScreenSharingService : VectorCallViewEvents()
     }
    diff --git a/vector/src/main/java/im/vector/app/features/call/VectorCallViewModel.kt b/vector/src/main/java/im/vector/app/features/call/VectorCallViewModel.kt
    index a26eec04f3..55a0219bfe 100644
    --- a/vector/src/main/java/im/vector/app/features/call/VectorCallViewModel.kt
    +++ b/vector/src/main/java/im/vector/app/features/call/VectorCallViewModel.kt
    @@ -16,7 +16,6 @@
     
     package im.vector.app.features.call
     
    -import androidx.lifecycle.viewModelScope
     import com.airbnb.mvrx.Fail
     import com.airbnb.mvrx.Loading
     import com.airbnb.mvrx.MavericksViewModelFactory
    @@ -26,7 +25,6 @@ import dagger.assisted.AssistedFactory
     import dagger.assisted.AssistedInject
     import im.vector.app.core.di.MavericksAssistedViewModelFactory
     import im.vector.app.core.di.hiltMavericksViewModelFactory
    -import im.vector.app.core.extensions.exhaustive
     import im.vector.app.core.platform.VectorViewModel
     import im.vector.app.features.call.audio.CallAudioManager
     import im.vector.app.features.call.dialpad.DialPadLookup
    @@ -44,6 +42,7 @@ import org.matrix.android.sdk.api.session.Session
     import org.matrix.android.sdk.api.session.call.CallState
     import org.matrix.android.sdk.api.session.call.MxCall
     import org.matrix.android.sdk.api.session.call.MxPeerConnectionState
    +import org.matrix.android.sdk.api.session.getRoomSummary
     import org.matrix.android.sdk.api.session.room.model.call.supportCallTransfer
     import org.matrix.android.sdk.api.util.MatrixItem
     
    @@ -258,27 +257,30 @@ class VectorCallViewModel @AssistedInject constructor(
     
         override fun handle(action: VectorCallViewActions) = withState { state ->
             when (action) {
    -            VectorCallViewActions.EndCall              -> call?.endCall()
    -            VectorCallViewActions.AcceptCall           -> {
    +            VectorCallViewActions.EndCall                        -> {
    +                call?.endCall()
    +                _viewEvents.post(VectorCallViewEvents.StopScreenSharingService)
    +            }
    +            VectorCallViewActions.AcceptCall                     -> {
                     setState {
                         copy(callState = Loading())
                     }
                     call?.acceptIncomingCall()
                 }
    -            VectorCallViewActions.DeclineCall          -> {
    +            VectorCallViewActions.DeclineCall                    -> {
                     setState {
                         copy(callState = Loading())
                     }
                     call?.endCall()
                 }
    -            VectorCallViewActions.ToggleMute           -> {
    +            VectorCallViewActions.ToggleMute                     -> {
                     val muted = state.isAudioMuted
                     call?.muteCall(!muted)
                     setState {
                         copy(isAudioMuted = !muted)
                     }
                 }
    -            VectorCallViewActions.ToggleVideo          -> {
    +            VectorCallViewActions.ToggleVideo                    -> {
                     if (state.isVideoCall) {
                         val videoEnabled = state.isVideoEnabled
                         call?.enableVideo(!videoEnabled)
    @@ -288,19 +290,19 @@ class VectorCallViewModel @AssistedInject constructor(
                     }
                     Unit
                 }
    -            VectorCallViewActions.ToggleHoldResume     -> {
    +            VectorCallViewActions.ToggleHoldResume               -> {
                     val isRemoteOnHold = state.isRemoteOnHold
                     call?.updateRemoteOnHold(!isRemoteOnHold)
                 }
    -            is VectorCallViewActions.ChangeAudioDevice -> {
    +            is VectorCallViewActions.ChangeAudioDevice           -> {
                     callManager.audioManager.setAudioDevice(action.device)
                 }
    -            VectorCallViewActions.SwitchSoundDevice    -> {
    +            VectorCallViewActions.SwitchSoundDevice              -> {
                     _viewEvents.post(
                             VectorCallViewEvents.ShowSoundDeviceChooser(state.availableDevices, state.device)
                     )
                 }
    -            VectorCallViewActions.HeadSetButtonPressed -> {
    +            VectorCallViewActions.HeadSetButtonPressed           -> {
                     if (state.callState.invoke() is CallState.LocalRinging) {
                         // accept call
                         call?.acceptIncomingCall()
    @@ -311,20 +313,20 @@ class VectorCallViewModel @AssistedInject constructor(
                     }
                     Unit
                 }
    -            VectorCallViewActions.ToggleCamera         -> {
    +            VectorCallViewActions.ToggleCamera                   -> {
                     call?.switchCamera()
                 }
    -            VectorCallViewActions.ToggleHDSD           -> {
    +            VectorCallViewActions.ToggleHDSD                     -> {
                     if (!state.isVideoCall) return@withState
                     call?.setCaptureFormat(if (state.isHD) CaptureFormat.SD else CaptureFormat.HD)
                 }
    -            VectorCallViewActions.OpenDialPad          -> {
    +            VectorCallViewActions.OpenDialPad                    -> {
                     _viewEvents.post(VectorCallViewEvents.ShowDialPad)
                 }
    -            is VectorCallViewActions.SendDtmfDigit     -> {
    +            is VectorCallViewActions.SendDtmfDigit               -> {
                     call?.sendDtmfDigit(action.digit)
                 }
    -            VectorCallViewActions.InitiateCallTransfer -> {
    +            VectorCallViewActions.InitiateCallTransfer           -> {
                     call?.updateRemoteOnHold(true)
                     _viewEvents.post(
                             VectorCallViewEvents.ShowCallTransferScreen
    @@ -336,14 +338,39 @@ class VectorCallViewModel @AssistedInject constructor(
                 is VectorCallViewActions.CallTransferSelectionResult -> {
                     handleCallTransferSelectionResult(action.callTransferResult)
                 }
    -            VectorCallViewActions.TransferCall         -> {
    +            VectorCallViewActions.TransferCall                   -> {
                     handleCallTransfer()
                 }
    -            is VectorCallViewActions.SwitchCall        -> {
    +            is VectorCallViewActions.SwitchCall                  -> {
                     setState { VectorCallViewState(action.callArgs) }
                     setupCallWithCurrentState()
                 }
    -        }.exhaustive
    +            is VectorCallViewActions.ToggleScreenSharing         -> {
    +                handleToggleScreenSharing(state.isSharingScreen)
    +            }
    +            is VectorCallViewActions.StartScreenSharing          -> {
    +                call?.startSharingScreen()
    +                setState {
    +                    copy(isSharingScreen = true)
    +                }
    +            }
    +        }
    +    }
    +
    +    private fun handleToggleScreenSharing(isSharingScreen: Boolean) {
    +        if (isSharingScreen) {
    +            call?.stopSharingScreen()
    +            setState {
    +                copy(isSharingScreen = false)
    +            }
    +            _viewEvents.post(
    +                    VectorCallViewEvents.StopScreenSharingService
    +            )
    +        } else {
    +            _viewEvents.post(
    +                    VectorCallViewEvents.ShowScreenSharingPermissionDialog
    +            )
    +        }
         }
     
         private fun handleCallTransfer() {
    @@ -358,7 +385,7 @@ class VectorCallViewModel @AssistedInject constructor(
             when (result) {
                 is CallTransferResult.ConnectWithUserId      -> connectWithUserId(result)
                 is CallTransferResult.ConnectWithPhoneNumber -> connectWithPhoneNumber(result)
    -        }.exhaustive
    +        }
         }
     
         private fun connectWithUserId(result: CallTransferResult.ConnectWithUserId) {
    diff --git a/vector/src/main/java/im/vector/app/features/call/VectorCallViewState.kt b/vector/src/main/java/im/vector/app/features/call/VectorCallViewState.kt
    index 2d33cbf9b9..2cd819b5f5 100644
    --- a/vector/src/main/java/im/vector/app/features/call/VectorCallViewState.kt
    +++ b/vector/src/main/java/im/vector/app/features/call/VectorCallViewState.kt
    @@ -42,7 +42,8 @@ data class VectorCallViewState(
             val callInfo: CallInfo? = null,
             val formattedDuration: String = "",
             val canOpponentBeTransferred: Boolean = false,
    -        val transferee: TransfereeState = TransfereeState.NoTransferee
    +        val transferee: TransfereeState = TransfereeState.NoTransferee,
    +        val isSharingScreen: Boolean = false
     ) : MavericksState {
     
         sealed class TransfereeState {
    diff --git a/vector/src/main/java/im/vector/app/features/call/audio/API21AudioDeviceDetector.kt b/vector/src/main/java/im/vector/app/features/call/audio/API21AudioDeviceDetector.kt
    index eafd1eab20..15d213e017 100644
    --- a/vector/src/main/java/im/vector/app/features/call/audio/API21AudioDeviceDetector.kt
    +++ b/vector/src/main/java/im/vector/app/features/call/audio/API21AudioDeviceDetector.kt
    @@ -27,7 +27,6 @@ import im.vector.app.core.services.BluetoothHeadsetReceiver
     import im.vector.app.core.services.WiredHeadsetStateReceiver
     import org.matrix.android.sdk.api.logger.LoggerTag
     import timber.log.Timber
    -import java.util.HashSet
     
     private val loggerTag = LoggerTag("API21AudioDeviceDetector", LoggerTag.VOIP)
     
    diff --git a/vector/src/main/java/im/vector/app/features/call/audio/API23AudioDeviceDetector.kt b/vector/src/main/java/im/vector/app/features/call/audio/API23AudioDeviceDetector.kt
    index fb17338fd1..ccf79cc02d 100644
    --- a/vector/src/main/java/im/vector/app/features/call/audio/API23AudioDeviceDetector.kt
    +++ b/vector/src/main/java/im/vector/app/features/call/audio/API23AudioDeviceDetector.kt
    @@ -21,7 +21,6 @@ import android.media.AudioManager
     import android.os.Build
     import androidx.annotation.RequiresApi
     import timber.log.Timber
    -import java.util.HashSet
     
     @RequiresApi(Build.VERSION_CODES.M)
     internal class API23AudioDeviceDetector(private val audioManager: AudioManager,
    @@ -33,10 +32,11 @@ internal class API23AudioDeviceDetector(private val audioManager: AudioManager,
             val deviceInfos = audioManager.getDevices(AudioManager.GET_DEVICES_OUTPUTS)
             for (info in deviceInfos) {
                 when (info.type) {
    -                AudioDeviceInfo.TYPE_BLUETOOTH_SCO -> devices.add(CallAudioManager.Device.WirelessHeadset(info.productName.toString()))
    -                AudioDeviceInfo.TYPE_BUILTIN_EARPIECE -> devices.add(CallAudioManager.Device.Phone)
    -                AudioDeviceInfo.TYPE_BUILTIN_SPEAKER -> devices.add(CallAudioManager.Device.Speaker)
    -                AudioDeviceInfo.TYPE_WIRED_HEADPHONES, AudioDeviceInfo.TYPE_WIRED_HEADSET, TYPE_USB_HEADSET -> devices.add(CallAudioManager.Device.Headset)
    +                AudioDeviceInfo.TYPE_BLUETOOTH_SCO                   -> devices.add(CallAudioManager.Device.WirelessHeadset(info.productName.toString()))
    +                AudioDeviceInfo.TYPE_BUILTIN_EARPIECE                -> devices.add(CallAudioManager.Device.Phone)
    +                AudioDeviceInfo.TYPE_BUILTIN_SPEAKER                 -> devices.add(CallAudioManager.Device.Speaker)
    +                AudioDeviceInfo.TYPE_WIRED_HEADPHONES,
    +                AudioDeviceInfo.TYPE_WIRED_HEADSET, TYPE_USB_HEADSET -> devices.add(CallAudioManager.Device.Headset)
                 }
             }
             callAudioManager.replaceDevices(devices)
    diff --git a/vector/src/main/java/im/vector/app/features/call/audio/CallAudioManager.kt b/vector/src/main/java/im/vector/app/features/call/audio/CallAudioManager.kt
    index f4f56f9844..d4793640d3 100644
    --- a/vector/src/main/java/im/vector/app/features/call/audio/CallAudioManager.kt
    +++ b/vector/src/main/java/im/vector/app/features/call/audio/CallAudioManager.kt
    @@ -25,7 +25,6 @@ import androidx.core.content.getSystemService
     import im.vector.app.R
     import org.matrix.android.sdk.api.extensions.orFalse
     import timber.log.Timber
    -import java.util.HashSet
     import java.util.concurrent.Executors
     
     class CallAudioManager(private val context: Context, val configChange: (() -> Unit)?) {
    diff --git a/vector/src/main/java/im/vector/app/features/call/audio/DefaultAudioDeviceRouter.kt b/vector/src/main/java/im/vector/app/features/call/audio/DefaultAudioDeviceRouter.kt
    index fd85ce075f..c3500c7bc2 100644
    --- a/vector/src/main/java/im/vector/app/features/call/audio/DefaultAudioDeviceRouter.kt
    +++ b/vector/src/main/java/im/vector/app/features/call/audio/DefaultAudioDeviceRouter.kt
    @@ -95,7 +95,7 @@ class DefaultAudioDeviceRouter(private val audioManager: AudioManager,
         override fun onAudioFocusChange(focusChange: Int) {
             callAudioManager.runInAudioThread {
                 when (focusChange) {
    -                AudioManager.AUDIOFOCUS_GAIN -> {
    +                AudioManager.AUDIOFOCUS_GAIN                                                                                          -> {
                         Timber.d(" Audio focus gained")
                         if (audioFocusLost) {
                             callAudioManager.resetAudioRoute()
    diff --git a/vector/src/main/java/im/vector/app/features/call/conference/JitsiCallViewModel.kt b/vector/src/main/java/im/vector/app/features/call/conference/JitsiCallViewModel.kt
    index d04bebfd1b..0ea380734d 100644
    --- a/vector/src/main/java/im/vector/app/features/call/conference/JitsiCallViewModel.kt
    +++ b/vector/src/main/java/im/vector/app/features/call/conference/JitsiCallViewModel.kt
    @@ -17,7 +17,6 @@
     package im.vector.app.features.call.conference
     
     import androidx.lifecycle.asFlow
    -import androidx.lifecycle.viewModelScope
     import com.airbnb.mvrx.Fail
     import com.airbnb.mvrx.MavericksViewModelFactory
     import com.airbnb.mvrx.Success
    @@ -27,7 +26,6 @@ import dagger.assisted.AssistedFactory
     import dagger.assisted.AssistedInject
     import im.vector.app.core.di.MavericksAssistedViewModelFactory
     import im.vector.app.core.di.hiltMavericksViewModelFactory
    -import im.vector.app.core.extensions.exhaustive
     import im.vector.app.core.platform.VectorViewModel
     import kotlinx.coroutines.Job
     import kotlinx.coroutines.delay
    @@ -103,7 +101,7 @@ class JitsiCallViewModel @AssistedInject constructor(
             when (action) {
                 is JitsiCallViewActions.SwitchTo      -> handleSwitchTo(action)
                 JitsiCallViewActions.OnConferenceLeft -> handleOnConferenceLeft()
    -        }.exhaustive
    +        }
         }
     
         private fun handleSwitchTo(action: JitsiCallViewActions.SwitchTo) = withState { state ->
    diff --git a/vector/src/main/java/im/vector/app/features/call/conference/JitsiService.kt b/vector/src/main/java/im/vector/app/features/call/conference/JitsiService.kt
    index b691296ba3..07062fc732 100644
    --- a/vector/src/main/java/im/vector/app/features/call/conference/JitsiService.kt
    +++ b/vector/src/main/java/im/vector/app/features/call/conference/JitsiService.kt
    @@ -31,11 +31,12 @@ import org.jitsi.meet.sdk.JitsiMeetUserInfo
     import org.matrix.android.sdk.api.extensions.tryOrNull
     import org.matrix.android.sdk.api.raw.RawService
     import org.matrix.android.sdk.api.session.Session
    +import org.matrix.android.sdk.api.session.getRoomSummary
     import org.matrix.android.sdk.api.session.widgets.model.Widget
     import org.matrix.android.sdk.api.session.widgets.model.WidgetType
    +import org.matrix.android.sdk.api.util.MatrixJsonParser
     import org.matrix.android.sdk.api.util.appendParamToUrl
     import org.matrix.android.sdk.api.util.toMatrixItem
    -import org.matrix.android.sdk.internal.di.MoshiProvider
     import java.net.URL
     import java.util.UUID
     import javax.inject.Inject
    @@ -99,7 +100,7 @@ class JitsiService @Inject constructor(
         }
     
         suspend fun joinConference(roomId: String, jitsiWidget: Widget, enableVideo: Boolean): JitsiCallViewEvents.JoinConference {
    -        val me = session.getRoomMember(session.myUserId, roomId)?.toMatrixItem()
    +        val me = session.roomService().getRoomMember(session.myUserId, roomId)?.toMatrixItem()
             val userDisplayName = me?.getBestName()
             val userAvatar = me?.avatarUrl?.let { session.contentUrlResolver().resolveFullSize(it) }
             val userInfo = JitsiMeetUserInfo().apply {
    @@ -168,7 +169,7 @@ class JitsiService @Inject constructor(
             return tryOrNull {
                 val response = session.getOkHttpClient().newCall(request).await()
                 val json = response.body?.string() ?: return null
    -            MoshiProvider.providesMoshi().adapter(JitsiWellKnown::class.java).fromJson(json)?.auth
    +            MatrixJsonParser.getMoshi().adapter(JitsiWellKnown::class.java).fromJson(json)?.auth
             }
         }
     }
    diff --git a/vector/src/main/java/im/vector/app/features/call/conference/RemoveJitsiWidgetView.kt b/vector/src/main/java/im/vector/app/features/call/conference/RemoveJitsiWidgetView.kt
    index fd7fc31e6d..e7659fb3e6 100644
    --- a/vector/src/main/java/im/vector/app/features/call/conference/RemoveJitsiWidgetView.kt
    +++ b/vector/src/main/java/im/vector/app/features/call/conference/RemoveJitsiWidgetView.kt
    @@ -88,7 +88,7 @@ import org.matrix.android.sdk.api.session.room.model.Membership
         fun render(roomDetailViewState: RoomDetailViewState) {
             val summary = roomDetailViewState.asyncRoomSummary()
             val newState = if (summary?.membership != Membership.JOIN ||
    -                roomDetailViewState.isWebRTCCallOptionAvailable() ||
    +                roomDetailViewState.isCallOptionAvailable() ||
                     !roomDetailViewState.isAllowedToManageWidgets ||
                     roomDetailViewState.jitsiState.widgetId == null) {
                 State.Unmount
    diff --git a/vector/src/main/java/im/vector/app/features/call/conference/VectorJitsiActivity.kt b/vector/src/main/java/im/vector/app/features/call/conference/VectorJitsiActivity.kt
    index a668f66f30..5a12337e4f 100644
    --- a/vector/src/main/java/im/vector/app/features/call/conference/VectorJitsiActivity.kt
    +++ b/vector/src/main/java/im/vector/app/features/call/conference/VectorJitsiActivity.kt
    @@ -35,7 +35,6 @@ import com.facebook.react.modules.core.PermissionListener
     import com.google.android.material.dialog.MaterialAlertDialogBuilder
     import dagger.hilt.android.AndroidEntryPoint
     import im.vector.app.R
    -import im.vector.app.core.extensions.exhaustive
     import im.vector.app.core.platform.VectorBaseActivity
     import im.vector.app.databinding.ActivityJitsiBinding
     import kotlinx.parcelize.Parcelize
    @@ -79,7 +78,7 @@ class VectorJitsiActivity : VectorBaseActivity(), JitsiMee
                     JitsiCallViewEvents.FailJoiningConference         -> handleFailJoining()
                     JitsiCallViewEvents.Finish                        -> finish()
                     JitsiCallViewEvents.LeaveConference               -> handleLeaveConference()
    -            }.exhaustive
    +            }
             }
             lifecycle.addObserver(ConferenceEventObserver(this, this::onBroadcastEvent))
         }
    diff --git a/vector/src/main/java/im/vector/app/features/call/dialpad/DialPadLookup.kt b/vector/src/main/java/im/vector/app/features/call/dialpad/DialPadLookup.kt
    index eadccab4f6..e835a74fd6 100644
    --- a/vector/src/main/java/im/vector/app/features/call/dialpad/DialPadLookup.kt
    +++ b/vector/src/main/java/im/vector/app/features/call/dialpad/DialPadLookup.kt
    @@ -47,9 +47,9 @@ class DialPadLookup @Inject constructor(
                 if (nativeUserId == session.myUserId) {
                     throw Failure.NumberIsYours
                 }
    -            session.getExistingDirectRoomWithUser(nativeUserId)
    +            session.roomService().getExistingDirectRoomWithUser(nativeUserId)
                 // if there is not, just create a DM with the sip user
    -            ?: directRoomHelper.ensureDMExists(sipUserId)
    +                    ?: directRoomHelper.ensureDMExists(sipUserId)
             } else {
                 // do the same if there is no corresponding native user.
                 directRoomHelper.ensureDMExists(sipUserId)
    diff --git a/vector/src/main/java/im/vector/app/features/call/lookup/CallUserMapper.kt b/vector/src/main/java/im/vector/app/features/call/lookup/CallUserMapper.kt
    index 0820b34124..8e3df50817 100644
    --- a/vector/src/main/java/im/vector/app/features/call/lookup/CallUserMapper.kt
    +++ b/vector/src/main/java/im/vector/app/features/call/lookup/CallUserMapper.kt
    @@ -20,6 +20,7 @@ import org.matrix.android.sdk.api.extensions.tryOrNull
     import org.matrix.android.sdk.api.session.Session
     import org.matrix.android.sdk.api.session.events.model.toContent
     import org.matrix.android.sdk.api.session.events.model.toModel
    +import org.matrix.android.sdk.api.session.getRoom
     import org.matrix.android.sdk.api.session.room.Room
     import org.matrix.android.sdk.api.session.room.accountdata.RoomAccountDataTypes
     import org.matrix.android.sdk.api.session.room.model.create.CreateRoomParams
    @@ -61,7 +62,7 @@ class CallUserMapper(private val session: Session, private val protocolsChecker:
             val nativeLookup = session.sipNativeLookup(inviterId).firstOrNull() ?: return
             if (nativeLookup.fields.containsKey("is_virtual")) {
                 val nativeUser = nativeLookup.userId
    -            val nativeRoomId = session.getExistingDirectRoomWithUser(nativeUser)
    +            val nativeRoomId = session.roomService().getExistingDirectRoomWithUser(nativeUser)
                 if (nativeRoomId != null) {
                     // It's a virtual room with a matching native room, so set the room account data. This
                     // will make sure we know where how to map calls and also allow us know not to display
    @@ -82,7 +83,7 @@ class CallUserMapper(private val session: Session, private val protocolsChecker:
         }
     
         private suspend fun ensureVirtualRoomExists(userId: String, nativeRoomId: String): String {
    -        val existingDMRoom = tryOrNull { session.getExistingDirectRoomWithUser(userId) }
    +        val existingDMRoom = tryOrNull { session.roomService().getExistingDirectRoomWithUser(userId) }
             val roomId: String
             if (existingDMRoom != null) {
                 roomId = existingDMRoom
    @@ -92,7 +93,7 @@ class CallUserMapper(private val session: Session, private val protocolsChecker:
                     setDirectMessage()
                     creationContent[RoomAccountDataTypes.EVENT_TYPE_VIRTUAL_ROOM] = nativeRoomId
                 }
    -            roomId = session.createRoom(roomParams)
    +            roomId = session.roomService().createRoom(roomParams)
             }
             return roomId
         }
    diff --git a/vector/src/main/java/im/vector/app/features/call/transfer/CallTransferActivity.kt b/vector/src/main/java/im/vector/app/features/call/transfer/CallTransferActivity.kt
    index d8eede6a55..81173568b5 100644
    --- a/vector/src/main/java/im/vector/app/features/call/transfer/CallTransferActivity.kt
    +++ b/vector/src/main/java/im/vector/app/features/call/transfer/CallTransferActivity.kt
    @@ -26,7 +26,6 @@ import com.google.android.material.tabs.TabLayoutMediator
     import dagger.hilt.android.AndroidEntryPoint
     import im.vector.app.R
     import im.vector.app.core.error.ErrorFormatter
    -import im.vector.app.core.extensions.exhaustive
     import im.vector.app.core.platform.VectorBaseActivity
     import im.vector.app.databinding.ActivityCallTransferBinding
     import kotlinx.parcelize.Parcelize
    @@ -57,7 +56,7 @@ class CallTransferActivity : VectorBaseActivity() {
             callTransferViewModel.observeViewEvents {
                 when (it) {
                     is CallTransferViewEvents.Complete -> handleComplete()
    -            }.exhaustive
    +            }
             }
     
             sectionsPagerAdapter = CallTransferPagerAdapter(this)
    @@ -71,7 +70,6 @@ class CallTransferActivity : VectorBaseActivity() {
             }.attach()
             setupToolbar(views.callTransferToolbar)
                     .allowBack()
    -        views.callTransferToolbar.title = getString(R.string.call_transfer_title)
             setupConnectAction()
         }
     
    diff --git a/vector/src/main/java/im/vector/app/features/call/transfer/CallTransferViewModel.kt b/vector/src/main/java/im/vector/app/features/call/transfer/CallTransferViewModel.kt
    index 1765b58a02..67f0949dcf 100644
    --- a/vector/src/main/java/im/vector/app/features/call/transfer/CallTransferViewModel.kt
    +++ b/vector/src/main/java/im/vector/app/features/call/transfer/CallTransferViewModel.kt
    @@ -31,7 +31,7 @@ import org.matrix.android.sdk.api.session.call.MxCall
     
     class CallTransferViewModel @AssistedInject constructor(@Assisted initialState: CallTransferViewState,
                                                             private val callManager: WebRtcCallManager) :
    -    VectorViewModel(initialState) {
    +        VectorViewModel(initialState) {
     
         @AssistedFactory
         interface Factory : MavericksAssistedViewModelFactory {
    @@ -62,5 +62,5 @@ class CallTransferViewModel @AssistedInject constructor(@Assisted initialState:
             call?.removeListener(callListener)
         }
     
    -    override fun handle(action: EmptyAction) { }
    +    override fun handle(action: EmptyAction) {}
     }
    diff --git a/vector/src/main/java/im/vector/app/features/call/webrtc/PeerConnectionObserver.kt b/vector/src/main/java/im/vector/app/features/call/webrtc/PeerConnectionObserver.kt
    index 99c26c5ebe..c776951f93 100644
    --- a/vector/src/main/java/im/vector/app/features/call/webrtc/PeerConnectionObserver.kt
    +++ b/vector/src/main/java/im/vector/app/features/call/webrtc/PeerConnectionObserver.kt
    @@ -37,13 +37,13 @@ class PeerConnectionObserver(private val webRtcCall: WebRtcCall) : PeerConnectio
                  * Every ICE transport used by the connection is either in use (state "connected" or "completed")
                  * or is closed (state "closed"); in addition, at least one transport is either "connected" or "completed"
                  */
    -            PeerConnection.PeerConnectionState.CONNECTED -> {
    +            PeerConnection.PeerConnectionState.CONNECTED    -> {
                     webRtcCall.mxCall.state = CallState.Connected(MxPeerConnectionState.CONNECTED)
                 }
                 /**
                  * One or more of the ICE transports on the connection is in the "failed" state.
                  */
    -            PeerConnection.PeerConnectionState.FAILED -> {
    +            PeerConnection.PeerConnectionState.FAILED       -> {
                     // This can be temporary, e.g when other ice not yet received...
                     // webRtcCall.mxCall.state = CallState.ERROR
                     webRtcCall.mxCall.state = CallState.Connected(MxPeerConnectionState.FAILED)
    @@ -58,7 +58,7 @@ class PeerConnectionObserver(private val webRtcCall: WebRtcCall) : PeerConnectio
                      * One or more of the ICE transports are currently in the process of establishing a connection;
                      * that is, their RTCIceConnectionState is either "checking" or "connected", and no transports are in the "failed" state
                      */
    -            PeerConnection.PeerConnectionState.CONNECTING -> {
    +            PeerConnection.PeerConnectionState.CONNECTING   -> {
                     webRtcCall.mxCall.state = CallState.Connected(MxPeerConnectionState.CONNECTING)
                 }
                 /**
    @@ -66,7 +66,7 @@ class PeerConnectionObserver(private val webRtcCall: WebRtcCall) : PeerConnectio
                  * This value was in the RTCSignalingState enum (and therefore found by reading the value of the signalingState)
                  * property until the May 13, 2016 draft of the specification.
                  */
    -            PeerConnection.PeerConnectionState.CLOSED -> {
    +            PeerConnection.PeerConnectionState.CLOSED       -> {
                     webRtcCall.mxCall.state = CallState.Connected(MxPeerConnectionState.CLOSED)
                 }
                 /**
    @@ -76,7 +76,7 @@ class PeerConnectionObserver(private val webRtcCall: WebRtcCall) : PeerConnectio
                 PeerConnection.PeerConnectionState.DISCONNECTED -> {
                     webRtcCall.mxCall.state = CallState.Connected(MxPeerConnectionState.DISCONNECTED)
                 }
    -            null -> {
    +            null                                            -> {
                 }
             }
         }
    @@ -101,14 +101,14 @@ class PeerConnectionObserver(private val webRtcCall: WebRtcCall) : PeerConnectio
                  * the ICE agent is gathering addresses or is waiting to be given remote candidates through
                  * calls to RTCPeerConnection.addIceCandidate() (or both).
                  */
    -            PeerConnection.IceConnectionState.NEW -> {
    +            PeerConnection.IceConnectionState.NEW          -> {
                 }
                 /**
                  * The ICE agent has been given one or more remote candidates and is checking pairs of local and remote candidates
                  * against one another to try to find a compatible match, but has not yet found a pair which will allow
                  * the peer connection to be made. It's possible that gathering of candidates is also still underway.
                  */
    -            PeerConnection.IceConnectionState.CHECKING -> {
    +            PeerConnection.IceConnectionState.CHECKING     -> {
                 }
     
                 /**
    @@ -117,7 +117,7 @@ class PeerConnectionObserver(private val webRtcCall: WebRtcCall) : PeerConnectio
                  * It's possible that gathering is still underway, and it's also possible that the ICE agent is still checking
                  * candidates against one another looking for a better connection to use.
                  */
    -            PeerConnection.IceConnectionState.CONNECTED -> {
    +            PeerConnection.IceConnectionState.CONNECTED    -> {
                 }
                 /**
                  * Checks to ensure that components are still connected failed for at least one component of the RTCPeerConnection.
    @@ -131,18 +131,18 @@ class PeerConnectionObserver(private val webRtcCall: WebRtcCall) : PeerConnectio
                  * compatible matches for all components of the connection.
                  * It is, however, possible that the ICE agent did find compatible connections for some components.
                  */
    -            PeerConnection.IceConnectionState.FAILED -> {
    +            PeerConnection.IceConnectionState.FAILED       -> {
                     webRtcCall.onRenegotiationNeeded(restartIce = true)
                 }
                 /**
                  *  The ICE agent has finished gathering candidates, has checked all pairs against one another, and has found a connection for all components.
                  */
    -            PeerConnection.IceConnectionState.COMPLETED -> {
    +            PeerConnection.IceConnectionState.COMPLETED    -> {
                 }
                 /**
                  * The ICE agent for this RTCPeerConnection has shut down and is no longer handling requests.
                  */
    -            PeerConnection.IceConnectionState.CLOSED -> {
    +            PeerConnection.IceConnectionState.CLOSED       -> {
                 }
             }
         }
    diff --git a/vector/src/main/java/im/vector/app/features/call/webrtc/ScreenCaptureService.kt b/vector/src/main/java/im/vector/app/features/call/webrtc/ScreenCaptureService.kt
    new file mode 100644
    index 0000000000..f1a4975751
    --- /dev/null
    +++ b/vector/src/main/java/im/vector/app/features/call/webrtc/ScreenCaptureService.kt
    @@ -0,0 +1,56 @@
    +/*
    + * Copyright (c) 2022 New Vector Ltd
    + *
    + * Licensed under the Apache License, Version 2.0 (the "License");
    + * you may not use this file except in compliance with the License.
    + * You may obtain a copy of the License at
    + *
    + *     http://www.apache.org/licenses/LICENSE-2.0
    + *
    + * Unless required by applicable law or agreed to in writing, software
    + * distributed under the License is distributed on an "AS IS" BASIS,
    + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    + * See the License for the specific language governing permissions and
    + * limitations under the License.
    + */
    +
    +package im.vector.app.features.call.webrtc
    +
    +import android.content.Intent
    +import android.os.Binder
    +import android.os.IBinder
    +import dagger.hilt.android.AndroidEntryPoint
    +import im.vector.app.core.services.VectorService
    +import im.vector.app.features.notifications.NotificationUtils
    +import javax.inject.Inject
    +
    +@AndroidEntryPoint
    +class ScreenCaptureService : VectorService() {
    +
    +    @Inject lateinit var notificationUtils: NotificationUtils
    +    private val binder = LocalBinder()
    +
    +    override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
    +        showStickyNotification()
    +
    +        return START_STICKY
    +    }
    +
    +    private fun showStickyNotification() {
    +        val notificationId = System.currentTimeMillis().toInt()
    +        val notification = notificationUtils.buildScreenSharingNotification()
    +        startForeground(notificationId, notification)
    +    }
    +
    +    override fun onBind(intent: Intent?): IBinder {
    +        return binder
    +    }
    +
    +    fun stopService() {
    +        stopSelf()
    +    }
    +
    +    inner class LocalBinder : Binder() {
    +        fun getService(): ScreenCaptureService = this@ScreenCaptureService
    +    }
    +}
    diff --git a/vector/src/main/java/im/vector/app/features/call/webrtc/ScreenCaptureServiceConnection.kt b/vector/src/main/java/im/vector/app/features/call/webrtc/ScreenCaptureServiceConnection.kt
    new file mode 100644
    index 0000000000..922e9676a8
    --- /dev/null
    +++ b/vector/src/main/java/im/vector/app/features/call/webrtc/ScreenCaptureServiceConnection.kt
    @@ -0,0 +1,54 @@
    +/*
    + * Copyright (c) 2022 New Vector Ltd
    + *
    + * Licensed under the Apache License, Version 2.0 (the "License");
    + * you may not use this file except in compliance with the License.
    + * You may obtain a copy of the License at
    + *
    + *     http://www.apache.org/licenses/LICENSE-2.0
    + *
    + * Unless required by applicable law or agreed to in writing, software
    + * distributed under the License is distributed on an "AS IS" BASIS,
    + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    + * See the License for the specific language governing permissions and
    + * limitations under the License.
    + */
    +
    +package im.vector.app.features.call.webrtc
    +
    +import android.content.ComponentName
    +import android.content.Context
    +import android.content.Intent
    +import android.content.ServiceConnection
    +import android.os.IBinder
    +import javax.inject.Inject
    +
    +class ScreenCaptureServiceConnection @Inject constructor(
    +        private val context: Context
    +) : ServiceConnection {
    +
    +    private var isBound = false
    +    private var screenCaptureService: ScreenCaptureService? = null
    +
    +    fun bind() {
    +        if (!isBound) {
    +            Intent(context, ScreenCaptureService::class.java).also { intent ->
    +                context.bindService(intent, this, 0)
    +            }
    +        }
    +    }
    +
    +    fun stopScreenCapturing() {
    +        screenCaptureService?.stopService()
    +    }
    +
    +    override fun onServiceConnected(className: ComponentName, binder: IBinder) {
    +        screenCaptureService = (binder as ScreenCaptureService.LocalBinder).getService()
    +        isBound = true
    +    }
    +
    +    override fun onServiceDisconnected(className: ComponentName) {
    +        isBound = false
    +        screenCaptureService = null
    +    }
    +}
    diff --git a/vector/src/main/java/im/vector/app/features/call/webrtc/WebRtcCall.kt b/vector/src/main/java/im/vector/app/features/call/webrtc/WebRtcCall.kt
    index 90088c8475..fed61c3b15 100644
    --- a/vector/src/main/java/im/vector/app/features/call/webrtc/WebRtcCall.kt
    +++ b/vector/src/main/java/im/vector/app/features/call/webrtc/WebRtcCall.kt
    @@ -770,6 +770,14 @@ class WebRtcCall(
             return currentCaptureFormat
         }
     
    +    fun startSharingScreen() {
    +        // TODO. Will be handled within the next PR.
    +    }
    +
    +    fun stopSharingScreen() {
    +        // TODO. Will be handled within the next PR.
    +    }
    +
         private suspend fun release() {
             listeners.clear()
             mxCall.removeListener(this)
    @@ -981,7 +989,7 @@ class WebRtcCall(
                     val nativeUserId = session.sipNativeLookup(newAssertedIdentity.id!!).firstOrNull()?.userId
                     if (nativeUserId != null) {
                         val resolvedUser = tryOrNull {
    -                        session.resolveUser(nativeUserId)
    +                        session.userService().resolveUser(nativeUserId)
                         }
                         if (resolvedUser != null) {
                             remoteAssertedIdentity = newAssertedIdentity.copy(
    diff --git a/vector/src/main/java/im/vector/app/features/call/webrtc/WebRtcCallExt.kt b/vector/src/main/java/im/vector/app/features/call/webrtc/WebRtcCallExt.kt
    index ac9d169633..5def0a7e50 100644
    --- a/vector/src/main/java/im/vector/app/features/call/webrtc/WebRtcCallExt.kt
    +++ b/vector/src/main/java/im/vector/app/features/call/webrtc/WebRtcCallExt.kt
    @@ -18,6 +18,7 @@ package im.vector.app.features.call.webrtc
     
     import org.matrix.android.sdk.api.extensions.orFalse
     import org.matrix.android.sdk.api.session.Session
    +import org.matrix.android.sdk.api.session.getRoom
     import org.matrix.android.sdk.api.util.MatrixItem
     import org.matrix.android.sdk.api.util.toMatrixItem
     
    diff --git a/vector/src/main/java/im/vector/app/features/contactsbook/ContactsBookFragment.kt b/vector/src/main/java/im/vector/app/features/contactsbook/ContactsBookFragment.kt
    index ebd0089736..7425e0ae8a 100644
    --- a/vector/src/main/java/im/vector/app/features/contactsbook/ContactsBookFragment.kt
    +++ b/vector/src/main/java/im/vector/app/features/contactsbook/ContactsBookFragment.kt
    @@ -26,7 +26,6 @@ import com.airbnb.mvrx.activityViewModel
     import com.airbnb.mvrx.withState
     import im.vector.app.core.extensions.cleanup
     import im.vector.app.core.extensions.configureWith
    -import im.vector.app.core.extensions.exhaustive
     import im.vector.app.core.extensions.hideKeyboard
     import im.vector.app.core.platform.VectorBaseFragment
     import im.vector.app.core.utils.showIdentityServerConsentDialog
    @@ -73,7 +72,7 @@ class ContactsBookFragment @Inject constructor(
                 when (it) {
                     is ContactsBookViewEvents.Failure             -> showFailure(it.throwable)
                     is ContactsBookViewEvents.OnPoliciesRetrieved -> showConsentDialog(it)
    -            }.exhaustive
    +            }
             }
         }
     
    diff --git a/vector/src/main/java/im/vector/app/features/contactsbook/ContactsBookViewModel.kt b/vector/src/main/java/im/vector/app/features/contactsbook/ContactsBookViewModel.kt
    index 5678668b25..d016558764 100644
    --- a/vector/src/main/java/im/vector/app/features/contactsbook/ContactsBookViewModel.kt
    +++ b/vector/src/main/java/im/vector/app/features/contactsbook/ContactsBookViewModel.kt
    @@ -27,7 +27,6 @@ import im.vector.app.core.contacts.ContactsDataSource
     import im.vector.app.core.contacts.MappedContact
     import im.vector.app.core.di.MavericksAssistedViewModelFactory
     import im.vector.app.core.di.hiltMavericksViewModelFactory
    -import im.vector.app.core.extensions.exhaustive
     import im.vector.app.core.platform.VectorViewModel
     import im.vector.app.core.resources.StringProvider
     import im.vector.app.features.discovery.fetchIdentityServerWithTerms
    @@ -165,7 +164,7 @@ class ContactsBookViewModel @AssistedInject constructor(
                 is ContactsBookAction.OnlyBoundContacts -> handleOnlyBoundContacts(action)
                 ContactsBookAction.UserConsentGranted   -> handleUserConsentGranted()
                 ContactsBookAction.UserConsentRequest   -> handleUserConsentRequest()
    -        }.exhaustive
    +        }
         }
     
         private fun handleUserConsentRequest() {
    diff --git a/vector/src/main/java/im/vector/app/features/createdirect/CreateDirectRoomActivity.kt b/vector/src/main/java/im/vector/app/features/createdirect/CreateDirectRoomActivity.kt
    index 9df4f52d0f..0053ab0fc6 100644
    --- a/vector/src/main/java/im/vector/app/features/createdirect/CreateDirectRoomActivity.kt
    +++ b/vector/src/main/java/im/vector/app/features/createdirect/CreateDirectRoomActivity.kt
    @@ -35,7 +35,6 @@ import im.vector.app.R
     import im.vector.app.core.error.ErrorFormatter
     import im.vector.app.core.extensions.addFragment
     import im.vector.app.core.extensions.addFragmentToBackstack
    -import im.vector.app.core.extensions.exhaustive
     import im.vector.app.core.platform.SimpleFragmentActivity
     import im.vector.app.core.platform.WaitingViewData
     import im.vector.app.core.utils.PERMISSIONS_FOR_MEMBERS_SEARCH
    @@ -84,7 +83,7 @@ class CreateDirectRoomActivity : SimpleFragmentActivity() {
                             is UserListSharedAction.OnMenuItemSelected -> onMenuItemSelected(action)
                             UserListSharedAction.OpenPhoneBook         -> openPhoneBook()
                             UserListSharedAction.AddByQrCode           -> openAddByQrCode()
    -                    }.exhaustive
    +                    }
                     }
                     .launchIn(lifecycleScope)
             if (isFirstCreation()) {
    @@ -111,7 +110,7 @@ class CreateDirectRoomActivity : SimpleFragmentActivity() {
                         Toast.makeText(this, R.string.cannot_dm_self, Toast.LENGTH_SHORT).show()
                         finish()
                     }
    -            }.exhaustive
    +            }
             }
     
             qrViewModel.observeViewEvents {
    @@ -124,7 +123,7 @@ class CreateDirectRoomActivity : SimpleFragmentActivity() {
                         finish()
                     }
                     else                               -> Unit
    -            }.exhaustive
    +            }
             }
         }
     
    @@ -170,6 +169,7 @@ class CreateDirectRoomActivity : SimpleFragmentActivity() {
                 is Loading -> renderCreationLoading()
                 is Success -> renderCreationSuccess(state())
                 is Fail    -> renderCreationFailure(state.error)
    +            else       -> Unit
             }
         }
     
    diff --git a/vector/src/main/java/im/vector/app/features/createdirect/CreateDirectRoomViewModel.kt b/vector/src/main/java/im/vector/app/features/createdirect/CreateDirectRoomViewModel.kt
    index 9dd3ef6a9b..a507f0d099 100644
    --- a/vector/src/main/java/im/vector/app/features/createdirect/CreateDirectRoomViewModel.kt
    +++ b/vector/src/main/java/im/vector/app/features/createdirect/CreateDirectRoomViewModel.kt
    @@ -24,25 +24,30 @@ import dagger.assisted.AssistedFactory
     import dagger.assisted.AssistedInject
     import im.vector.app.core.di.MavericksAssistedViewModelFactory
     import im.vector.app.core.di.hiltMavericksViewModelFactory
    -import im.vector.app.core.extensions.exhaustive
     import im.vector.app.core.mvrx.runCatchingToAsync
     import im.vector.app.core.platform.VectorViewModel
    +import im.vector.app.features.analytics.AnalyticsTracker
    +import im.vector.app.features.analytics.plan.CreatedRoom
     import im.vector.app.features.raw.wellknown.getElementWellknown
     import im.vector.app.features.raw.wellknown.isE2EByDefault
     import im.vector.app.features.userdirectory.PendingSelection
     import kotlinx.coroutines.Dispatchers
     import kotlinx.coroutines.launch
    +import org.matrix.android.sdk.api.extensions.orFalse
     import org.matrix.android.sdk.api.raw.RawService
     import org.matrix.android.sdk.api.session.Session
    +import org.matrix.android.sdk.api.session.getUser
     import org.matrix.android.sdk.api.session.permalinks.PermalinkData
     import org.matrix.android.sdk.api.session.permalinks.PermalinkParser
     import org.matrix.android.sdk.api.session.room.model.create.CreateRoomParams
     import org.matrix.android.sdk.api.session.user.model.User
     
    -class CreateDirectRoomViewModel @AssistedInject constructor(@Assisted
    -                                                            initialState: CreateDirectRoomViewState,
    -                                                            private val rawService: RawService,
    -                                                            val session: Session) :
    +class CreateDirectRoomViewModel @AssistedInject constructor(
    +        @Assisted initialState: CreateDirectRoomViewState,
    +        private val rawService: RawService,
    +        val session: Session,
    +        val analyticsTracker: AnalyticsTracker
    +) :
             VectorViewModel(initialState) {
     
         @AssistedFactory
    @@ -56,7 +61,7 @@ class CreateDirectRoomViewModel @AssistedInject constructor(@Assisted
             when (action) {
                 is CreateDirectRoomAction.CreateRoomAndInviteSelectedUsers -> onSubmitInvitees(action.selections)
                 is CreateDirectRoomAction.QrScannedAction                  -> onCodeParsed(action)
    -        }.exhaustive
    +        }
         }
     
         private fun onCodeParsed(action: CreateDirectRoomAction.QrScannedAction) {
    @@ -70,7 +75,11 @@ class CreateDirectRoomViewModel @AssistedInject constructor(@Assisted
                     _viewEvents.post(CreateDirectRoomViewEvents.DmSelf)
                 } else {
                     // Try to get user from known users and fall back to creating a User object from MXID
    -                val qrInvitee = if (session.getUser(mxid) != null) session.getUser(mxid)!! else User(mxid, null, null)
    +                val qrInvitee = if (session.getUser(mxid) != null) {
    +                    session.getUser(mxid)!!
    +                } else {
    +                    User(mxid, null, null)
    +                }
                     onSubmitInvitees(setOf(PendingSelection.UserPendingSelection(qrInvitee)))
                 }
             }
    @@ -81,7 +90,7 @@ class CreateDirectRoomViewModel @AssistedInject constructor(@Assisted
          */
         private fun onSubmitInvitees(selections: Set) {
             val existingRoomId = selections.singleOrNull()?.getMxId()?.let { userId ->
    -            session.getExistingDirectRoomWithUser(userId)
    +            session.roomService().getExistingDirectRoomWithUser(userId)
             }
             if (existingRoomId != null) {
                 // Do not create a new DM, just tell that the creation is successful by passing the existing roomId
    @@ -108,15 +117,16 @@ class CreateDirectRoomViewModel @AssistedInject constructor(@Assisted
                                 when (it) {
                                     is PendingSelection.UserPendingSelection     -> invitedUserIds.add(it.user.userId)
                                     is PendingSelection.ThreePidPendingSelection -> invite3pids.add(it.threePid)
    -                            }.exhaustive
    +                            }
                             }
                             setDirectMessage()
                             enableEncryptionIfInvitedUsersSupportIt = adminE2EByDefault
                         }
     
                 val result = runCatchingToAsync {
    -                session.createRoom(roomParams)
    +                session.roomService().createRoom(roomParams)
                 }
    +            analyticsTracker.capture(CreatedRoom(isDM = roomParams.isDirect.orFalse()))
     
                 setState {
                     copy(
    diff --git a/vector/src/main/java/im/vector/app/features/createdirect/DirectRoomHelper.kt b/vector/src/main/java/im/vector/app/features/createdirect/DirectRoomHelper.kt
    index cf75bdf1b6..de2027f2a5 100644
    --- a/vector/src/main/java/im/vector/app/features/createdirect/DirectRoomHelper.kt
    +++ b/vector/src/main/java/im/vector/app/features/createdirect/DirectRoomHelper.kt
    @@ -16,8 +16,11 @@
     
     package im.vector.app.features.createdirect
     
    +import im.vector.app.features.analytics.AnalyticsTracker
    +import im.vector.app.features.analytics.plan.CreatedRoom
     import im.vector.app.features.raw.wellknown.getElementWellknown
     import im.vector.app.features.raw.wellknown.isE2EByDefault
    +import org.matrix.android.sdk.api.extensions.orFalse
     import org.matrix.android.sdk.api.extensions.tryOrNull
     import org.matrix.android.sdk.api.raw.RawService
     import org.matrix.android.sdk.api.session.Session
    @@ -26,11 +29,12 @@ import javax.inject.Inject
     
     class DirectRoomHelper @Inject constructor(
             private val rawService: RawService,
    -        private val session: Session
    +        private val session: Session,
    +        private val analyticsTracker: AnalyticsTracker
     ) {
     
         suspend fun ensureDMExists(userId: String): String {
    -        val existingRoomId = tryOrNull { session.getExistingDirectRoomWithUser(userId) }
    +        val existingRoomId = tryOrNull { session.roomService().getExistingDirectRoomWithUser(userId) }
             val roomId: String
             if (existingRoomId != null) {
                 roomId = existingRoomId
    @@ -44,7 +48,8 @@ class DirectRoomHelper @Inject constructor(
                     setDirectMessage()
                     enableEncryptionIfInvitedUsersSupportIt = adminE2EByDefault
                 }
    -            roomId = session.createRoom(roomParams)
    +            roomId = session.roomService().createRoom(roomParams)
    +            analyticsTracker.capture(CreatedRoom(isDM = roomParams.isDirect.orFalse()))
             }
             return roomId
         }
    diff --git a/vector/src/main/java/im/vector/app/features/crypto/keys/KeysExporter.kt b/vector/src/main/java/im/vector/app/features/crypto/keys/KeysExporter.kt
    index 3db67df8e1..f40f126d2c 100644
    --- a/vector/src/main/java/im/vector/app/features/crypto/keys/KeysExporter.kt
    +++ b/vector/src/main/java/im/vector/app/features/crypto/keys/KeysExporter.kt
    @@ -19,6 +19,7 @@ package im.vector.app.features.crypto.keys
     import android.content.Context
     import android.net.Uri
     import im.vector.app.core.dispatchers.CoroutineDispatchers
    +import im.vector.app.core.extensions.safeOpenOutputStream
     import kotlinx.coroutines.withContext
     import org.matrix.android.sdk.api.session.Session
     import javax.inject.Inject
    @@ -34,7 +35,7 @@ class KeysExporter @Inject constructor(
         suspend fun export(password: String, uri: Uri) {
             withContext(dispatchers.io) {
                 val data = session.cryptoService().exportRoomKeys(password)
    -            context.contentResolver.openOutputStream(uri)
    +            context.safeOpenOutputStream(uri)
                         ?.use { it.write(data) }
                         ?: throw IllegalStateException("Unable to open file for writing")
                 verifyExportedKeysOutputFileSize(uri, expectedSize = data.size.toLong())
    diff --git a/vector/src/main/java/im/vector/app/features/crypto/keys/KeysImporter.kt b/vector/src/main/java/im/vector/app/features/crypto/keys/KeysImporter.kt
    index 50c85c3e5f..9b1d29fa25 100644
    --- a/vector/src/main/java/im/vector/app/features/crypto/keys/KeysImporter.kt
    +++ b/vector/src/main/java/im/vector/app/features/crypto/keys/KeysImporter.kt
    @@ -23,7 +23,7 @@ import im.vector.app.core.resources.openResource
     import kotlinx.coroutines.Dispatchers
     import kotlinx.coroutines.withContext
     import org.matrix.android.sdk.api.session.Session
    -import org.matrix.android.sdk.internal.crypto.model.ImportRoomKeysResult
    +import org.matrix.android.sdk.api.session.crypto.model.ImportRoomKeysResult
     import javax.inject.Inject
     
     class KeysImporter @Inject constructor(
    diff --git a/vector/src/main/java/im/vector/app/features/crypto/keysbackup/restore/KeysBackupRestoreActivity.kt b/vector/src/main/java/im/vector/app/features/crypto/keysbackup/restore/KeysBackupRestoreActivity.kt
    index 6e5d7f5fab..a4f6587be4 100644
    --- a/vector/src/main/java/im/vector/app/features/crypto/keysbackup/restore/KeysBackupRestoreActivity.kt
    +++ b/vector/src/main/java/im/vector/app/features/crypto/keysbackup/restore/KeysBackupRestoreActivity.kt
    @@ -52,7 +52,7 @@ class KeysBackupRestoreActivity : SimpleFragmentActivity() {
             super.onBackPressed()
         }
     
    -    @Inject  lateinit var activeSessionHolder: ActiveSessionHolder
    +    @Inject lateinit var activeSessionHolder: ActiveSessionHolder
     
         override fun initUiAndData() {
             super.initUiAndData()
    diff --git a/vector/src/main/java/im/vector/app/features/crypto/keysbackup/restore/KeysBackupRestoreFromKeyFragment.kt b/vector/src/main/java/im/vector/app/features/crypto/keysbackup/restore/KeysBackupRestoreFromKeyFragment.kt
    index 40ad1372fb..42605a850b 100644
    --- a/vector/src/main/java/im/vector/app/features/crypto/keysbackup/restore/KeysBackupRestoreFromKeyFragment.kt
    +++ b/vector/src/main/java/im/vector/app/features/crypto/keysbackup/restore/KeysBackupRestoreFromKeyFragment.kt
    @@ -31,7 +31,7 @@ import org.matrix.android.sdk.api.extensions.tryOrNull
     import javax.inject.Inject
     
     class KeysBackupRestoreFromKeyFragment @Inject constructor() :
    -    VectorBaseFragment() {
    +        VectorBaseFragment() {
     
         override fun getBinding(inflater: LayoutInflater, container: ViewGroup?): FragmentKeysBackupRestoreFromKeyBinding {
             return FragmentKeysBackupRestoreFromKeyBinding.inflate(inflater, container, false)
    diff --git a/vector/src/main/java/im/vector/app/features/crypto/keysbackup/restore/KeysBackupRestoreSharedViewModel.kt b/vector/src/main/java/im/vector/app/features/crypto/keysbackup/restore/KeysBackupRestoreSharedViewModel.kt
    index 8362a3566e..6cfe0bf686 100644
    --- a/vector/src/main/java/im/vector/app/features/crypto/keysbackup/restore/KeysBackupRestoreSharedViewModel.kt
    +++ b/vector/src/main/java/im/vector/app/features/crypto/keysbackup/restore/KeysBackupRestoreSharedViewModel.kt
    @@ -29,13 +29,15 @@ import org.matrix.android.sdk.api.MatrixCallback
     import org.matrix.android.sdk.api.listeners.StepProgressListener
     import org.matrix.android.sdk.api.session.Session
     import org.matrix.android.sdk.api.session.crypto.crosssigning.KEYBACKUP_SECRET_SSSS_NAME
    +import org.matrix.android.sdk.api.session.crypto.keysbackup.KeysBackupLastVersionResult
     import org.matrix.android.sdk.api.session.crypto.keysbackup.KeysBackupService
    +import org.matrix.android.sdk.api.session.crypto.keysbackup.KeysVersionResult
    +import org.matrix.android.sdk.api.session.crypto.keysbackup.computeRecoveryKey
    +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.securestorage.KeyInfoResult
    -import org.matrix.android.sdk.internal.crypto.crosssigning.fromBase64
    -import org.matrix.android.sdk.internal.crypto.keysbackup.model.rest.KeysVersionResult
    -import org.matrix.android.sdk.internal.crypto.keysbackup.util.computeRecoveryKey
    -import org.matrix.android.sdk.internal.crypto.model.ImportRoomKeysResult
    -import org.matrix.android.sdk.internal.util.awaitCallback
    +import org.matrix.android.sdk.api.util.awaitCallback
    +import org.matrix.android.sdk.api.util.fromBase64
     import timber.log.Timber
     import javax.inject.Inject
     
    @@ -117,9 +119,9 @@ class KeysBackupRestoreSharedViewModel @Inject constructor(
     
             viewModelScope.launch(Dispatchers.IO) {
                 try {
    -                val version = awaitCallback {
    +                val version = awaitCallback {
                         keysBackup.getCurrentVersion(it)
    -                }
    +                }.toKeysVersionResult()
                     if (version?.version == null) {
                         loadingEvent.postValue(null)
                         _keyVersionResultError.postValue(LiveEvent(stringProvider.getString(R.string.keys_backup_get_version_error, "")))
    @@ -168,7 +170,7 @@ class KeysBackupRestoreSharedViewModel @Inject constructor(
         fun handleGotSecretFromSSSS(cipherData: String, alias: String) {
             try {
                 cipherData.fromBase64().inputStream().use { ins ->
    -                val res = session.loadSecureSecret>(ins, alias)
    +                val res = session.secureStorageService().loadSecureSecret>(ins, alias)
                     val secret = res?.get(KEYBACKUP_SECRET_SSSS_NAME)
                     if (secret == null) {
                         _navigateEvent.postValue(
    @@ -250,7 +252,7 @@ class KeysBackupRestoreSharedViewModel @Inject constructor(
                     ?: return false
     
             // Some sanity ?
    -        val defaultKeyResult = session.sharedSecretStorageService.getDefaultKey()
    +        val defaultKeyResult = session.sharedSecretStorageService().getDefaultKey()
             val keyInfo = (defaultKeyResult as? KeyInfoResult.Success)?.keyInfo
                     ?: return false
     
    diff --git a/vector/src/main/java/im/vector/app/features/crypto/keysbackup/settings/KeysBackupSettingViewState.kt b/vector/src/main/java/im/vector/app/features/crypto/keysbackup/settings/KeysBackupSettingViewState.kt
    index 438b502b42..7c1105277b 100644
    --- a/vector/src/main/java/im/vector/app/features/crypto/keysbackup/settings/KeysBackupSettingViewState.kt
    +++ b/vector/src/main/java/im/vector/app/features/crypto/keysbackup/settings/KeysBackupSettingViewState.kt
    @@ -20,8 +20,8 @@ import com.airbnb.mvrx.Async
     import com.airbnb.mvrx.MavericksState
     import com.airbnb.mvrx.Uninitialized
     import org.matrix.android.sdk.api.session.crypto.keysbackup.KeysBackupState
    -import org.matrix.android.sdk.internal.crypto.keysbackup.model.KeysBackupVersionTrust
    -import org.matrix.android.sdk.internal.crypto.keysbackup.model.rest.KeysVersionResult
    +import org.matrix.android.sdk.api.session.crypto.keysbackup.KeysBackupVersionTrust
    +import org.matrix.android.sdk.api.session.crypto.keysbackup.KeysVersionResult
     
     data class KeysBackupSettingViewState(val keysBackupVersionTrust: Async = Uninitialized,
                                           val keysBackupState: KeysBackupState? = null,
    diff --git a/vector/src/main/java/im/vector/app/features/crypto/keysbackup/settings/KeysBackupSettingsFragment.kt b/vector/src/main/java/im/vector/app/features/crypto/keysbackup/settings/KeysBackupSettingsFragment.kt
    index 50d5e56483..4d3ec9a820 100644
    --- a/vector/src/main/java/im/vector/app/features/crypto/keysbackup/settings/KeysBackupSettingsFragment.kt
    +++ b/vector/src/main/java/im/vector/app/features/crypto/keysbackup/settings/KeysBackupSettingsFragment.kt
    @@ -32,7 +32,7 @@ import im.vector.app.features.crypto.keysbackup.setup.KeysBackupSetupActivity
     import javax.inject.Inject
     
     class KeysBackupSettingsFragment @Inject constructor(private val keysBackupSettingsRecyclerViewController: KeysBackupSettingsRecyclerViewController) :
    -    VectorBaseFragment(),
    +        VectorBaseFragment(),
             KeysBackupSettingsRecyclerViewController.Listener {
     
         override fun getBinding(inflater: LayoutInflater, container: ViewGroup?): FragmentKeysBackupSettingsBinding {
    diff --git a/vector/src/main/java/im/vector/app/features/crypto/keysbackup/settings/KeysBackupSettingsRecyclerViewController.kt b/vector/src/main/java/im/vector/app/features/crypto/keysbackup/settings/KeysBackupSettingsRecyclerViewController.kt
    index 577572ef14..9bf8050939 100644
    --- a/vector/src/main/java/im/vector/app/features/crypto/keysbackup/settings/KeysBackupSettingsRecyclerViewController.kt
    +++ b/vector/src/main/java/im/vector/app/features/crypto/keysbackup/settings/KeysBackupSettingsRecyclerViewController.kt
    @@ -31,7 +31,7 @@ import im.vector.app.features.settings.VectorPreferences
     import im.vector.lib.core.utils.epoxy.charsequence.toEpoxyCharSequence
     import org.matrix.android.sdk.api.session.Session
     import org.matrix.android.sdk.api.session.crypto.keysbackup.KeysBackupState
    -import org.matrix.android.sdk.internal.crypto.keysbackup.model.KeysBackupVersionTrust
    +import org.matrix.android.sdk.api.session.crypto.keysbackup.KeysBackupVersionTrust
     import java.util.UUID
     import javax.inject.Inject
     
    @@ -140,6 +140,7 @@ class KeysBackupSettingsRecyclerViewController @Inject constructor(
     
                     isBackupAlreadySetup = true
                 }
    +            null                                       -> Unit
             }
     
             if (isBackupAlreadySetup) {
    diff --git a/vector/src/main/java/im/vector/app/features/crypto/keysbackup/settings/KeysBackupSettingsViewModel.kt b/vector/src/main/java/im/vector/app/features/crypto/keysbackup/settings/KeysBackupSettingsViewModel.kt
    index d6d9e10669..ca6edf0941 100644
    --- a/vector/src/main/java/im/vector/app/features/crypto/keysbackup/settings/KeysBackupSettingsViewModel.kt
    +++ b/vector/src/main/java/im/vector/app/features/crypto/keysbackup/settings/KeysBackupSettingsViewModel.kt
    @@ -33,7 +33,7 @@ import org.matrix.android.sdk.api.session.Session
     import org.matrix.android.sdk.api.session.crypto.keysbackup.KeysBackupService
     import org.matrix.android.sdk.api.session.crypto.keysbackup.KeysBackupState
     import org.matrix.android.sdk.api.session.crypto.keysbackup.KeysBackupStateListener
    -import org.matrix.android.sdk.internal.crypto.keysbackup.model.KeysBackupVersionTrust
    +import org.matrix.android.sdk.api.session.crypto.keysbackup.KeysBackupVersionTrust
     
     class KeysBackupSettingsViewModel @AssistedInject constructor(@Assisted initialState: KeysBackupSettingViewState,
                                                                   session: Session
    diff --git a/vector/src/main/java/im/vector/app/features/crypto/keysbackup/setup/KeysBackupSetupSharedViewModel.kt b/vector/src/main/java/im/vector/app/features/crypto/keysbackup/setup/KeysBackupSetupSharedViewModel.kt
    index 1141886689..34aa7ba0ee 100644
    --- a/vector/src/main/java/im/vector/app/features/crypto/keysbackup/setup/KeysBackupSetupSharedViewModel.kt
    +++ b/vector/src/main/java/im/vector/app/features/crypto/keysbackup/setup/KeysBackupSetupSharedViewModel.kt
    @@ -26,10 +26,11 @@ import im.vector.app.core.utils.LiveEvent
     import org.matrix.android.sdk.api.MatrixCallback
     import org.matrix.android.sdk.api.listeners.ProgressListener
     import org.matrix.android.sdk.api.session.Session
    +import org.matrix.android.sdk.api.session.crypto.keysbackup.KeysBackupLastVersionResult
     import org.matrix.android.sdk.api.session.crypto.keysbackup.KeysBackupService
    -import org.matrix.android.sdk.internal.crypto.keysbackup.model.MegolmBackupCreationInfo
    -import org.matrix.android.sdk.internal.crypto.keysbackup.model.rest.KeysVersion
    -import org.matrix.android.sdk.internal.crypto.keysbackup.model.rest.KeysVersionResult
    +import org.matrix.android.sdk.api.session.crypto.keysbackup.KeysVersion
    +import org.matrix.android.sdk.api.session.crypto.keysbackup.MegolmBackupCreationInfo
    +import org.matrix.android.sdk.api.session.crypto.keysbackup.toKeysVersionResult
     import timber.log.Timber
     import javax.inject.Inject
     
    @@ -150,9 +151,9 @@ class KeysBackupSetupSharedViewModel @Inject constructor() : ViewModel() {
     
             creatingBackupError.value = null
     
    -        keysBackup.getCurrentVersion(object : MatrixCallback {
    -            override fun onSuccess(data: KeysVersionResult?) {
    -                if (data?.version.isNullOrBlank() || forceOverride) {
    +        keysBackup.getCurrentVersion(object : MatrixCallback {
    +            override fun onSuccess(data: KeysBackupLastVersionResult) {
    +                if (data.toKeysVersionResult()?.version.isNullOrBlank() || forceOverride) {
                         processOnCreate()
                     } else {
                         loadingStatus.value = null
    diff --git a/vector/src/main/java/im/vector/app/features/crypto/keysbackup/setup/KeysBackupSetupStep3Fragment.kt b/vector/src/main/java/im/vector/app/features/crypto/keysbackup/setup/KeysBackupSetupStep3Fragment.kt
    index c1cd87b4c8..e5d7ade3ce 100644
    --- a/vector/src/main/java/im/vector/app/features/crypto/keysbackup/setup/KeysBackupSetupStep3Fragment.kt
    +++ b/vector/src/main/java/im/vector/app/features/crypto/keysbackup/setup/KeysBackupSetupStep3Fragment.kt
    @@ -30,6 +30,7 @@ import com.google.android.material.bottomsheet.BottomSheetDialog
     import com.google.android.material.dialog.MaterialAlertDialogBuilder
     import im.vector.app.R
     import im.vector.app.core.extensions.registerStartForActivityResult
    +import im.vector.app.core.extensions.safeOpenOutputStream
     import im.vector.app.core.platform.VectorBaseFragment
     import im.vector.app.core.utils.LiveEvent
     import im.vector.app.core.utils.copyToClipboard
    @@ -165,7 +166,7 @@ class KeysBackupSetupStep3Fragment @Inject constructor() : VectorBaseFragment
                                     os.write(data.toByteArray())
                                     os.flush()
    diff --git a/vector/src/main/java/im/vector/app/features/crypto/keysrequest/KeyRequestHandler.kt b/vector/src/main/java/im/vector/app/features/crypto/keysrequest/KeyRequestHandler.kt
    index 85250a94ce..0fbb18e63c 100644
    --- a/vector/src/main/java/im/vector/app/features/crypto/keysrequest/KeyRequestHandler.kt
    +++ b/vector/src/main/java/im/vector/app/features/crypto/keysrequest/KeyRequestHandler.kt
    @@ -24,18 +24,18 @@ import im.vector.app.features.popup.DefaultVectorAlert
     import im.vector.app.features.popup.PopupAlertManager
     import org.matrix.android.sdk.api.MatrixCallback
     import org.matrix.android.sdk.api.session.Session
    +import org.matrix.android.sdk.api.session.crypto.crosssigning.DeviceTrustLevel
     import org.matrix.android.sdk.api.session.crypto.keyshare.GossipingRequestListener
    +import org.matrix.android.sdk.api.session.crypto.model.CryptoDeviceInfo
    +import org.matrix.android.sdk.api.session.crypto.model.DeviceInfo
    +import org.matrix.android.sdk.api.session.crypto.model.IncomingRequestCancellation
    +import org.matrix.android.sdk.api.session.crypto.model.IncomingRoomKeyRequest
    +import org.matrix.android.sdk.api.session.crypto.model.IncomingSecretShareRequest
    +import org.matrix.android.sdk.api.session.crypto.model.MXUsersDevicesMap
     import org.matrix.android.sdk.api.session.crypto.verification.SasVerificationTransaction
     import org.matrix.android.sdk.api.session.crypto.verification.VerificationService
     import org.matrix.android.sdk.api.session.crypto.verification.VerificationTransaction
     import org.matrix.android.sdk.api.session.crypto.verification.VerificationTxState
    -import org.matrix.android.sdk.internal.crypto.IncomingRequestCancellation
    -import org.matrix.android.sdk.internal.crypto.IncomingRoomKeyRequest
    -import org.matrix.android.sdk.internal.crypto.IncomingSecretShareRequest
    -import org.matrix.android.sdk.internal.crypto.crosssigning.DeviceTrustLevel
    -import org.matrix.android.sdk.internal.crypto.model.CryptoDeviceInfo
    -import org.matrix.android.sdk.internal.crypto.model.MXUsersDevicesMap
    -import org.matrix.android.sdk.internal.crypto.model.rest.DeviceInfo
     import timber.log.Timber
     import javax.inject.Inject
     import javax.inject.Singleton
    diff --git a/vector/src/main/java/im/vector/app/features/crypto/quads/SharedSecureStorageActivity.kt b/vector/src/main/java/im/vector/app/features/crypto/quads/SharedSecureStorageActivity.kt
    index b317ac95ad..0a105064d5 100644
    --- a/vector/src/main/java/im/vector/app/features/crypto/quads/SharedSecureStorageActivity.kt
    +++ b/vector/src/main/java/im/vector/app/features/crypto/quads/SharedSecureStorageActivity.kt
    @@ -116,12 +116,13 @@ class SharedSecureStorageActivity :
                 is SharedSecureStorageViewEvent.FinishSuccess        -> {
                     val dataResult = Intent()
                     dataResult.putExtra(EXTRA_DATA_RESULT, it.cypherResult)
    -                setResult(Activity.RESULT_OK, dataResult)
    +                setResult(RESULT_OK, dataResult)
                     finish()
                 }
                 is SharedSecureStorageViewEvent.ShowResetBottomSheet -> {
                     navigator.open4SSetup(this, SetupMode.HARD_RESET)
                 }
    +            else                                                 -> Unit
             }
         }
     
    diff --git a/vector/src/main/java/im/vector/app/features/crypto/quads/SharedSecureStorageViewModel.kt b/vector/src/main/java/im/vector/app/features/crypto/quads/SharedSecureStorageViewModel.kt
    index 8994ad901b..445cc70527 100644
    --- a/vector/src/main/java/im/vector/app/features/crypto/quads/SharedSecureStorageViewModel.kt
    +++ b/vector/src/main/java/im/vector/app/features/crypto/quads/SharedSecureStorageViewModel.kt
    @@ -29,7 +29,6 @@ import dagger.assisted.AssistedInject
     import im.vector.app.R
     import im.vector.app.core.di.MavericksAssistedViewModelFactory
     import im.vector.app.core.di.hiltMavericksViewModelFactory
    -import im.vector.app.core.extensions.exhaustive
     import im.vector.app.core.platform.VectorViewModel
     import im.vector.app.core.platform.WaitingViewData
     import im.vector.app.core.resources.StringProvider
    @@ -42,8 +41,8 @@ import org.matrix.android.sdk.api.session.Session
     import org.matrix.android.sdk.api.session.securestorage.IntegrityResult
     import org.matrix.android.sdk.api.session.securestorage.KeyInfoResult
     import org.matrix.android.sdk.api.session.securestorage.RawBytesKeySpec
    +import org.matrix.android.sdk.api.util.toBase64NoPadding
     import org.matrix.android.sdk.flow.flow
    -import org.matrix.android.sdk.internal.crypto.crosssigning.toBase64NoPadding
     import timber.log.Timber
     import java.io.ByteArrayOutputStream
     
    @@ -77,7 +76,7 @@ class SharedSecureStorageViewModel @AssistedInject constructor(
             @Assisted private val initialState: SharedSecureStorageViewState,
             private val stringProvider: StringProvider,
             private val session: Session) :
    -    VectorViewModel(initialState) {
    +        VectorViewModel(initialState) {
     
         @AssistedFactory
         interface Factory : MavericksAssistedViewModelFactory {
    @@ -88,8 +87,8 @@ class SharedSecureStorageViewModel @AssistedInject constructor(
             setState {
                 copy(userId = session.myUserId)
             }
    -        val integrityResult = session.sharedSecretStorageService.checkShouldBeAbleToAccessSecrets(initialState.requestedSecrets, initialState.keyId)
    -        if (integrityResult  !is IntegrityResult.Success) {
    +        val integrityResult = session.sharedSecretStorageService().checkShouldBeAbleToAccessSecrets(initialState.requestedSecrets, initialState.keyId)
    +        if (integrityResult !is IntegrityResult.Success) {
                 _viewEvents.post(
                         SharedSecureStorageViewEvent.Error(
                                 stringProvider.getString(R.string.enter_secret_storage_invalid),
    @@ -97,8 +96,8 @@ class SharedSecureStorageViewModel @AssistedInject constructor(
                         )
                 )
             }
    -        val keyResult = initialState.keyId?.let { session.sharedSecretStorageService.getKey(it) }
    -                ?: session.sharedSecretStorageService.getDefaultKey()
    +        val keyResult = initialState.keyId?.let { session.sharedSecretStorageService().getKey(it) }
    +                ?: session.sharedSecretStorageService().getDefaultKey()
     
             if (!keyResult.isSuccess()) {
                 _viewEvents.post(SharedSecureStorageViewEvent.Dismiss)
    @@ -135,14 +134,14 @@ class SharedSecureStorageViewModel @AssistedInject constructor(
     
         override fun handle(action: SharedSecureStorageAction) = withState {
             when (action) {
    -            is SharedSecureStorageAction.Cancel                   -> handleCancel()
    -            is SharedSecureStorageAction.SubmitPassphrase         -> handleSubmitPassphrase(action)
    -            SharedSecureStorageAction.UseKey                      -> handleUseKey()
    -            is SharedSecureStorageAction.SubmitKey                -> handleSubmitKey(action)
    -            SharedSecureStorageAction.Back                        -> handleBack()
    -            SharedSecureStorageAction.ForgotResetAll              -> handleResetAll()
    -            SharedSecureStorageAction.DoResetAll                  -> handleDoResetAll()
    -        }.exhaustive
    +            is SharedSecureStorageAction.Cancel           -> handleCancel()
    +            is SharedSecureStorageAction.SubmitPassphrase -> handleSubmitPassphrase(action)
    +            SharedSecureStorageAction.UseKey              -> handleUseKey()
    +            is SharedSecureStorageAction.SubmitKey        -> handleSubmitKey(action)
    +            SharedSecureStorageAction.Back                -> handleBack()
    +            SharedSecureStorageAction.ForgotResetAll      -> handleResetAll()
    +            SharedSecureStorageAction.DoResetAll          -> handleDoResetAll()
    +        }
         }
     
         private fun handleDoResetAll() {
    @@ -206,7 +205,7 @@ class SharedSecureStorageViewModel @AssistedInject constructor(
             viewModelScope.launch(Dispatchers.IO) {
                 runCatching {
                     val recoveryKey = action.recoveryKey
    -                val keyInfoResult = session.sharedSecretStorageService.getDefaultKey()
    +                val keyInfoResult = session.sharedSecretStorageService().getDefaultKey()
                     if (!keyInfoResult.isSuccess()) {
                         _viewEvents.post(SharedSecureStorageViewEvent.HideModalLoading)
                         _viewEvents.post(SharedSecureStorageViewEvent.Error(stringProvider.getString(R.string.failed_to_access_secure_storage)))
    @@ -229,7 +228,7 @@ class SharedSecureStorageViewModel @AssistedInject constructor(
                     withContext(Dispatchers.IO) {
                         initialState.requestedSecrets.forEach {
                             if (session.accountDataService().getUserAccountDataEvent(it) != null) {
    -                            val res = session.sharedSecretStorageService.getSecret(
    +                            val res = session.sharedSecretStorageService().getSecret(
                                         name = it,
                                         keyId = keyInfo.id,
                                         secretKey = keySpec)
    @@ -244,7 +243,7 @@ class SharedSecureStorageViewModel @AssistedInject constructor(
                     _viewEvents.post(SharedSecureStorageViewEvent.HideModalLoading)
                     val safeForIntentCypher = ByteArrayOutputStream().also {
                         it.use {
    -                        session.securelyStoreObject(decryptedSecretMap as Map, initialState.resultKeyStoreAlias, it)
    +                        session.secureStorageService().securelyStoreObject(decryptedSecretMap as Map, initialState.resultKeyStoreAlias, it)
                         }
                     }.toByteArray().toBase64NoPadding()
                     _viewEvents.post(SharedSecureStorageViewEvent.FinishSuccess(safeForIntentCypher))
    @@ -263,7 +262,7 @@ class SharedSecureStorageViewModel @AssistedInject constructor(
             viewModelScope.launch(Dispatchers.IO) {
                 runCatching {
                     val passphrase = action.passphrase
    -                val keyInfoResult = session.sharedSecretStorageService.getDefaultKey()
    +                val keyInfoResult = session.sharedSecretStorageService().getDefaultKey()
                     if (!keyInfoResult.isSuccess()) {
                         _viewEvents.post(SharedSecureStorageViewEvent.HideModalLoading)
                         _viewEvents.post(SharedSecureStorageViewEvent.Error("Cannot find ssss key"))
    @@ -298,7 +297,7 @@ class SharedSecureStorageViewModel @AssistedInject constructor(
                     withContext(Dispatchers.IO) {
                         initialState.requestedSecrets.forEach {
                             if (session.accountDataService().getUserAccountDataEvent(it) != null) {
    -                            val res = session.sharedSecretStorageService.getSecret(
    +                            val res = session.sharedSecretStorageService().getSecret(
                                         name = it,
                                         keyId = keyInfo.id,
                                         secretKey = keySpec)
    @@ -313,7 +312,7 @@ class SharedSecureStorageViewModel @AssistedInject constructor(
                     _viewEvents.post(SharedSecureStorageViewEvent.HideModalLoading)
                     val safeForIntentCypher = ByteArrayOutputStream().also {
                         it.use {
    -                        session.securelyStoreObject(decryptedSecretMap as Map, initialState.resultKeyStoreAlias, it)
    +                        session.secureStorageService().securelyStoreObject(decryptedSecretMap as Map, initialState.resultKeyStoreAlias, it)
                         }
                     }.toByteArray().toBase64NoPadding()
                     _viewEvents.post(SharedSecureStorageViewEvent.FinishSuccess(safeForIntentCypher))
    diff --git a/vector/src/main/java/im/vector/app/features/crypto/quads/SharedSecuredStorageKeyFragment.kt b/vector/src/main/java/im/vector/app/features/crypto/quads/SharedSecuredStorageKeyFragment.kt
    index 8e7f11f0f5..fd660367ae 100644
    --- a/vector/src/main/java/im/vector/app/features/crypto/quads/SharedSecuredStorageKeyFragment.kt
    +++ b/vector/src/main/java/im/vector/app/features/crypto/quads/SharedSecuredStorageKeyFragment.kt
    @@ -77,6 +77,7 @@ class SharedSecuredStorageKeyFragment @Inject constructor() : VectorBaseFragment
                     is SharedSecureStorageViewEvent.KeyInlineError -> {
                         views.ssssKeyEnterTil.error = it.message
                     }
    +                else                                           -> Unit
                 }
             }
     
    diff --git a/vector/src/main/java/im/vector/app/features/crypto/quads/SharedSecuredStoragePassphraseFragment.kt b/vector/src/main/java/im/vector/app/features/crypto/quads/SharedSecuredStoragePassphraseFragment.kt
    index 70c1003773..41507f2722 100644
    --- a/vector/src/main/java/im/vector/app/features/crypto/quads/SharedSecuredStoragePassphraseFragment.kt
    +++ b/vector/src/main/java/im/vector/app/features/crypto/quads/SharedSecuredStoragePassphraseFragment.kt
    @@ -86,6 +86,7 @@ class SharedSecuredStoragePassphraseFragment @Inject constructor(
                     is SharedSecureStorageViewEvent.InlineError -> {
                         views.ssssPassphraseEnterTil.error = it.message
                     }
    +                else                                        -> Unit
                 }
             }
     
    diff --git a/vector/src/main/java/im/vector/app/features/crypto/quads/SharedSecuredStorageResetAllFragment.kt b/vector/src/main/java/im/vector/app/features/crypto/quads/SharedSecuredStorageResetAllFragment.kt
    index 200b2b73c2..c0d0aa8e76 100644
    --- a/vector/src/main/java/im/vector/app/features/crypto/quads/SharedSecuredStorageResetAllFragment.kt
    +++ b/vector/src/main/java/im/vector/app/features/crypto/quads/SharedSecuredStorageResetAllFragment.kt
    @@ -30,7 +30,7 @@ import im.vector.app.features.roommemberprofile.devices.DeviceListBottomSheet
     import javax.inject.Inject
     
     class SharedSecuredStorageResetAllFragment @Inject constructor() :
    -    VectorBaseFragment() {
    +        VectorBaseFragment() {
     
         override fun getBinding(inflater: LayoutInflater, container: ViewGroup?): FragmentSsssResetAllBinding {
             return FragmentSsssResetAllBinding.inflate(inflater, container, false)
    diff --git a/vector/src/main/java/im/vector/app/features/crypto/recover/BackupToQuadSMigrationTask.kt b/vector/src/main/java/im/vector/app/features/crypto/recover/BackupToQuadSMigrationTask.kt
    index 74bab9b0b6..2092fe0f00 100644
    --- a/vector/src/main/java/im/vector/app/features/crypto/recover/BackupToQuadSMigrationTask.kt
    +++ b/vector/src/main/java/im/vector/app/features/crypto/recover/BackupToQuadSMigrationTask.kt
    @@ -24,15 +24,14 @@ import org.matrix.android.sdk.api.NoOpMatrixCallback
     import org.matrix.android.sdk.api.listeners.ProgressListener
     import org.matrix.android.sdk.api.session.Session
     import org.matrix.android.sdk.api.session.crypto.crosssigning.KEYBACKUP_SECRET_SSSS_NAME
    +import org.matrix.android.sdk.api.session.crypto.keysbackup.computeRecoveryKey
    +import org.matrix.android.sdk.api.session.crypto.keysbackup.extractCurveKeyFromRecoveryKey
     import org.matrix.android.sdk.api.session.securestorage.EmptyKeySigner
     import org.matrix.android.sdk.api.session.securestorage.RawBytesKeySpec
     import org.matrix.android.sdk.api.session.securestorage.SharedSecretStorageService
     import org.matrix.android.sdk.api.session.securestorage.SsssKeyCreationInfo
    -import org.matrix.android.sdk.internal.crypto.crosssigning.toBase64NoPadding
    -import org.matrix.android.sdk.internal.crypto.keysbackup.deriveKey
    -import org.matrix.android.sdk.internal.crypto.keysbackup.util.computeRecoveryKey
    -import org.matrix.android.sdk.internal.crypto.keysbackup.util.extractCurveKeyFromRecoveryKey
    -import org.matrix.android.sdk.internal.util.awaitCallback
    +import org.matrix.android.sdk.api.util.awaitCallback
    +import org.matrix.android.sdk.api.util.toBase64NoPadding
     import timber.log.Timber
     import java.util.UUID
     import javax.inject.Inject
    @@ -62,7 +61,7 @@ class BackupToQuadSMigrationTask @Inject constructor(
                 // We need to use the current secret for keybackup and use it as the new master key for SSSS
                 // Then we need to put back the backup key in sss
                 val keysBackupService = session.cryptoService().keysBackupService()
    -            val quadS = session.sharedSecretStorageService
    +            val quadS = session.sharedSecretStorageService()
     
                 val version = keysBackupService.keysBackupVersion ?: return Result.NoKeyBackupVersion
     
    @@ -72,14 +71,18 @@ class BackupToQuadSMigrationTask @Inject constructor(
                             extractCurveKeyFromRecoveryKey(params.recoveryKey)
                         } else if (!params.passphrase.isNullOrEmpty() && version.getAuthDataAsMegolmBackupAuthData()?.privateKeySalt != null) {
                             version.getAuthDataAsMegolmBackupAuthData()?.let { authData ->
    -                            deriveKey(params.passphrase, authData.privateKeySalt!!, authData.privateKeyIterations!!, object : ProgressListener {
    -                                override fun onProgress(progress: Int, total: Int) {
    -                                    params.progressListener?.onProgress(WaitingViewData(
    -                                            stringProvider.getString(R.string.bootstrap_progress_checking_backup_with_info,
    -                                                    "$progress/$total")
    -                                    ))
    -                                }
    -                            })
    +                            keysBackupService.computePrivateKey(
    +                                    params.passphrase,
    +                                    authData.privateKeySalt!!,
    +                                    authData.privateKeyIterations!!,
    +                                    object : ProgressListener {
    +                                        override fun onProgress(progress: Int, total: Int) {
    +                                            params.progressListener?.onProgress(WaitingViewData(
    +                                                    stringProvider.getString(R.string.bootstrap_progress_checking_backup_with_info,
    +                                                            "$progress/$total")
    +                                            ))
    +                                        }
    +                                    })
                             }
                         } else null)
                                 ?: return Result.IllegalParams
    diff --git a/vector/src/main/java/im/vector/app/features/crypto/recover/BootstrapActions.kt b/vector/src/main/java/im/vector/app/features/crypto/recover/BootstrapActions.kt
    index 363416b7de..395b4d0475 100644
    --- a/vector/src/main/java/im/vector/app/features/crypto/recover/BootstrapActions.kt
    +++ b/vector/src/main/java/im/vector/app/features/crypto/recover/BootstrapActions.kt
    @@ -37,7 +37,7 @@ sealed class BootstrapActions : VectorViewModelAction {
         data class UpdateCandidatePassphrase(val pass: String) : BootstrapActions()
         data class UpdateConfirmCandidatePassphrase(val pass: String) : BootstrapActions()
     
    -//    data class ReAuth(val pass: String) : BootstrapActions()
    +    //    data class ReAuth(val pass: String) : BootstrapActions()
         object RecoveryKeySaved : BootstrapActions()
         object Completed : BootstrapActions()
         object SaveReqQueryStarted : BootstrapActions()
    diff --git a/vector/src/main/java/im/vector/app/features/crypto/recover/BootstrapBottomSheet.kt b/vector/src/main/java/im/vector/app/features/crypto/recover/BootstrapBottomSheet.kt
    index 8448422a56..57a8ad68b7 100644
    --- a/vector/src/main/java/im/vector/app/features/crypto/recover/BootstrapBottomSheet.kt
    +++ b/vector/src/main/java/im/vector/app/features/crypto/recover/BootstrapBottomSheet.kt
    @@ -36,7 +36,6 @@ import com.google.android.material.dialog.MaterialAlertDialogBuilder
     import dagger.hilt.android.AndroidEntryPoint
     import im.vector.app.R
     import im.vector.app.core.extensions.commitTransaction
    -import im.vector.app.core.extensions.exhaustive
     import im.vector.app.core.extensions.registerStartForActivityResult
     import im.vector.app.core.extensions.toMvRxBundle
     import im.vector.app.core.platform.VectorBaseBottomSheetDialogFragment
    @@ -65,7 +64,7 @@ class BootstrapBottomSheet : VectorBaseBottomSheetDialogFragment
             if (activityResult.resultCode == Activity.RESULT_OK) {
                 when (activityResult.data?.extras?.getString(ReAuthActivity.RESULT_FLOW_TYPE)) {
    -                LoginFlowTypes.SSO -> {
    +                LoginFlowTypes.SSO      -> {
                         viewModel.handle(BootstrapActions.SsoAuthDone)
                     }
                     LoginFlowTypes.PASSWORD -> {
    @@ -209,7 +208,7 @@ class BootstrapBottomSheet : VectorBaseBottomSheetDialogFragment() {
    +        VectorBaseFragment() {
     
         override fun getBinding(inflater: LayoutInflater, container: ViewGroup?): FragmentBootstrapEnterPassphraseBinding {
             return FragmentBootstrapEnterPassphraseBinding.inflate(inflater, container, false)
    diff --git a/vector/src/main/java/im/vector/app/features/crypto/recover/BootstrapCrossSigningTask.kt b/vector/src/main/java/im/vector/app/features/crypto/recover/BootstrapCrossSigningTask.kt
    index cc863346aa..753e9f1942 100644
    --- a/vector/src/main/java/im/vector/app/features/crypto/recover/BootstrapCrossSigningTask.kt
    +++ b/vector/src/main/java/im/vector/app/features/crypto/recover/BootstrapCrossSigningTask.kt
    @@ -28,16 +28,17 @@ import org.matrix.android.sdk.api.session.crypto.crosssigning.KEYBACKUP_SECRET_S
     import org.matrix.android.sdk.api.session.crypto.crosssigning.MASTER_KEY_SSSS_NAME
     import org.matrix.android.sdk.api.session.crypto.crosssigning.SELF_SIGNING_KEY_SSSS_NAME
     import org.matrix.android.sdk.api.session.crypto.crosssigning.USER_SIGNING_KEY_SSSS_NAME
    +import org.matrix.android.sdk.api.session.crypto.keysbackup.KeysBackupLastVersionResult
    +import org.matrix.android.sdk.api.session.crypto.keysbackup.KeysVersion
    +import org.matrix.android.sdk.api.session.crypto.keysbackup.MegolmBackupCreationInfo
    +import org.matrix.android.sdk.api.session.crypto.keysbackup.extractCurveKeyFromRecoveryKey
    +import org.matrix.android.sdk.api.session.crypto.keysbackup.toKeysVersionResult
     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.SsssKeyCreationInfo
     import org.matrix.android.sdk.api.session.securestorage.SsssKeySpec
    -import org.matrix.android.sdk.internal.crypto.crosssigning.toBase64NoPadding
    -import org.matrix.android.sdk.internal.crypto.keysbackup.model.MegolmBackupCreationInfo
    -import org.matrix.android.sdk.internal.crypto.keysbackup.model.rest.KeysVersion
    -import org.matrix.android.sdk.internal.crypto.keysbackup.model.rest.KeysVersionResult
    -import org.matrix.android.sdk.internal.crypto.keysbackup.util.extractCurveKeyFromRecoveryKey
    -import org.matrix.android.sdk.internal.util.awaitCallback
    +import org.matrix.android.sdk.api.util.awaitCallback
    +import org.matrix.android.sdk.api.util.toBase64NoPadding
     import timber.log.Timber
     import java.util.UUID
     import javax.inject.Inject
    @@ -116,7 +117,7 @@ class BootstrapCrossSigningTask @Inject constructor(
     
             val keyInfo: SsssKeyCreationInfo
     
    -        val ssssService = session.sharedSecretStorageService
    +        val ssssService = session.sharedSecretStorageService()
     
             params.progressListener?.onProgress(
                     WaitingViewData(
    @@ -221,10 +222,9 @@ class BootstrapCrossSigningTask @Inject constructor(
                 Timber.d("## BootstrapCrossSigningTask: Creating 4S - Checking megolm backup")
     
                 // First ensure that in sync
    -            var serverVersion = awaitCallback {
    +            var serverVersion = awaitCallback {
                     session.cryptoService().keysBackupService().getCurrentVersion(it)
    -            }
    -
    +            }.toKeysVersionResult()
                 val knownMegolmSecret = session.cryptoService().keysBackupService().getKeyBackupRecoveryKeyInfo()
                 val isMegolmBackupSecretKnown = knownMegolmSecret != null && knownMegolmSecret.version == serverVersion?.version
                 val shouldCreateKeyBackup = serverVersion == null ||
    @@ -236,9 +236,9 @@ class BootstrapCrossSigningTask @Inject constructor(
                         awaitCallback {
                             session.cryptoService().keysBackupService().deleteBackup(serverVersion!!.version, it)
                         }
    -                    serverVersion = awaitCallback {
    +                    serverVersion = awaitCallback {
                             session.cryptoService().keysBackupService().getCurrentVersion(it)
    -                    }
    +                    }.toKeysVersionResult()
                     }
     
                     Timber.d("## BootstrapCrossSigningTask: Creating 4S - Create megolm backup")
    diff --git a/vector/src/main/java/im/vector/app/features/crypto/recover/BootstrapEnterPassphraseFragment.kt b/vector/src/main/java/im/vector/app/features/crypto/recover/BootstrapEnterPassphraseFragment.kt
    index 51430ba12e..ff6d109b3c 100644
    --- a/vector/src/main/java/im/vector/app/features/crypto/recover/BootstrapEnterPassphraseFragment.kt
    +++ b/vector/src/main/java/im/vector/app/features/crypto/recover/BootstrapEnterPassphraseFragment.kt
    @@ -36,7 +36,7 @@ import reactivecircus.flowbinding.android.widget.textChanges
     import javax.inject.Inject
     
     class BootstrapEnterPassphraseFragment @Inject constructor() :
    -    VectorBaseFragment() {
    +        VectorBaseFragment() {
     
         override fun getBinding(inflater: LayoutInflater, container: ViewGroup?): FragmentBootstrapEnterPassphraseBinding {
             return FragmentBootstrapEnterPassphraseBinding.inflate(inflater, container, false)
    diff --git a/vector/src/main/java/im/vector/app/features/crypto/recover/BootstrapMigrateBackupFragment.kt b/vector/src/main/java/im/vector/app/features/crypto/recover/BootstrapMigrateBackupFragment.kt
    index 429d51857c..2c0ccec9fb 100644
    --- a/vector/src/main/java/im/vector/app/features/crypto/recover/BootstrapMigrateBackupFragment.kt
    +++ b/vector/src/main/java/im/vector/app/features/crypto/recover/BootstrapMigrateBackupFragment.kt
    @@ -42,7 +42,7 @@ import im.vector.lib.core.utils.flow.throttleFirst
     import kotlinx.coroutines.flow.launchIn
     import kotlinx.coroutines.flow.onEach
     import org.matrix.android.sdk.api.extensions.tryOrNull
    -import org.matrix.android.sdk.internal.crypto.keysbackup.util.isValidRecoveryKey
    +import org.matrix.android.sdk.api.session.crypto.keysbackup.isValidRecoveryKey
     import reactivecircus.flowbinding.android.widget.editorActionEvents
     import reactivecircus.flowbinding.android.widget.textChanges
     import javax.inject.Inject
    diff --git a/vector/src/main/java/im/vector/app/features/crypto/recover/BootstrapSaveRecoveryKeyFragment.kt b/vector/src/main/java/im/vector/app/features/crypto/recover/BootstrapSaveRecoveryKeyFragment.kt
    index 8a41c7ce4d..efabae2f3a 100644
    --- a/vector/src/main/java/im/vector/app/features/crypto/recover/BootstrapSaveRecoveryKeyFragment.kt
    +++ b/vector/src/main/java/im/vector/app/features/crypto/recover/BootstrapSaveRecoveryKeyFragment.kt
    @@ -29,6 +29,7 @@ import com.airbnb.mvrx.parentFragmentViewModel
     import com.airbnb.mvrx.withState
     import im.vector.app.R
     import im.vector.app.core.extensions.registerStartForActivityResult
    +import im.vector.app.core.extensions.safeOpenOutputStream
     import im.vector.app.core.platform.VectorBaseFragment
     import im.vector.app.core.resources.ColorProvider
     import im.vector.app.core.utils.startSharePlainTextIntent
    @@ -81,7 +82,7 @@ class BootstrapSaveRecoveryKeyFragment @Inject constructor(
                 val uri = activityResult.data?.data ?: return@registerStartForActivityResult
                 lifecycleScope.launch(Dispatchers.IO) {
                     try {
    -                    sharedViewModel.handle(BootstrapActions.SaveKeyToUri(requireContext().contentResolver!!.openOutputStream(uri)!!))
    +                    sharedViewModel.handle(BootstrapActions.SaveKeyToUri(requireContext().safeOpenOutputStream(uri)!!))
                     } catch (failure: Throwable) {
                         sharedViewModel.handle(BootstrapActions.SaveReqFailed)
                     }
    diff --git a/vector/src/main/java/im/vector/app/features/crypto/recover/BootstrapSetupRecoveryKeyFragment.kt b/vector/src/main/java/im/vector/app/features/crypto/recover/BootstrapSetupRecoveryKeyFragment.kt
    index 2765dfefd3..3be2f020b8 100644
    --- a/vector/src/main/java/im/vector/app/features/crypto/recover/BootstrapSetupRecoveryKeyFragment.kt
    +++ b/vector/src/main/java/im/vector/app/features/crypto/recover/BootstrapSetupRecoveryKeyFragment.kt
    @@ -29,7 +29,7 @@ import im.vector.app.databinding.FragmentBootstrapSetupRecoveryBinding
     import javax.inject.Inject
     
     class BootstrapSetupRecoveryKeyFragment @Inject constructor() :
    -    VectorBaseFragment() {
    +        VectorBaseFragment() {
     
         override fun getBinding(inflater: LayoutInflater, container: ViewGroup?): FragmentBootstrapSetupRecoveryBinding {
             return FragmentBootstrapSetupRecoveryBinding.inflate(inflater, container, false)
    diff --git a/vector/src/main/java/im/vector/app/features/crypto/recover/BootstrapSharedViewModel.kt b/vector/src/main/java/im/vector/app/features/crypto/recover/BootstrapSharedViewModel.kt
    index f75ab634b8..a85c318a29 100644
    --- a/vector/src/main/java/im/vector/app/features/crypto/recover/BootstrapSharedViewModel.kt
    +++ b/vector/src/main/java/im/vector/app/features/crypto/recover/BootstrapSharedViewModel.kt
    @@ -29,7 +29,6 @@ import im.vector.app.R
     import im.vector.app.core.di.MavericksAssistedViewModelFactory
     import im.vector.app.core.di.hiltMavericksViewModelFactory
     import im.vector.app.core.error.ErrorFormatter
    -import im.vector.app.core.extensions.exhaustive
     import im.vector.app.core.platform.VectorViewModel
     import im.vector.app.core.platform.WaitingViewData
     import im.vector.app.core.resources.StringProvider
    @@ -44,12 +43,14 @@ import org.matrix.android.sdk.api.auth.registration.RegistrationFlowResponse
     import org.matrix.android.sdk.api.auth.registration.nextUncompletedStage
     import org.matrix.android.sdk.api.failure.Failure
     import org.matrix.android.sdk.api.session.Session
    +import org.matrix.android.sdk.api.session.crypto.keysbackup.KeysBackupLastVersionResult
    +import org.matrix.android.sdk.api.session.crypto.keysbackup.KeysVersionResult
    +import org.matrix.android.sdk.api.session.crypto.keysbackup.extractCurveKeyFromRecoveryKey
    +import org.matrix.android.sdk.api.session.crypto.keysbackup.toKeysVersionResult
     import org.matrix.android.sdk.api.session.securestorage.RawBytesKeySpec
    -import org.matrix.android.sdk.internal.crypto.crosssigning.fromBase64
    -import org.matrix.android.sdk.internal.crypto.keysbackup.model.rest.KeysVersionResult
    -import org.matrix.android.sdk.internal.crypto.keysbackup.util.extractCurveKeyFromRecoveryKey
    -import org.matrix.android.sdk.internal.crypto.model.rest.DefaultBaseAuth
    -import org.matrix.android.sdk.internal.util.awaitCallback
    +import org.matrix.android.sdk.api.session.uia.DefaultBaseAuth
    +import org.matrix.android.sdk.api.util.awaitCallback
    +import org.matrix.android.sdk.api.util.fromBase64
     import java.io.OutputStream
     import kotlin.coroutines.Continuation
     import kotlin.coroutines.resume
    @@ -104,9 +105,9 @@ class BootstrapSharedViewModel @AssistedInject constructor(
     
                     // We need to check if there is an existing backup
                     viewModelScope.launch(Dispatchers.IO) {
    -                    val version = awaitCallback {
    +                    val version = awaitCallback {
                             session.cryptoService().keysBackupService().getCurrentVersion(it)
    -                    }
    +                    }.toKeysVersionResult()
                         if (version == null) {
                             // we just resume plain bootstrap
                             doesKeyBackupExist = false
    @@ -245,7 +246,8 @@ class BootstrapSharedViewModel @AssistedInject constructor(
                     uiaContinuation?.resume(DefaultBaseAuth(session = pendingAuth?.session ?: ""))
                 }
                 is BootstrapActions.PasswordAuthDone                 -> {
    -                val decryptedPass = session.loadSecureSecret(action.password.fromBase64().inputStream(), ReAuthActivity.DEFAULT_RESULT_KEYSTORE_ALIAS)
    +                val decryptedPass = session.secureStorageService()
    +                        .loadSecureSecret(action.password.fromBase64().inputStream(), ReAuthActivity.DEFAULT_RESULT_KEYSTORE_ALIAS)
                     uiaContinuation?.resume(
                             UserPasswordAuth(
                                     session = pendingAuth?.session,
    @@ -259,7 +261,7 @@ class BootstrapSharedViewModel @AssistedInject constructor(
                         copy(step = BootstrapStep.AccountReAuth(stringProvider.getString(R.string.authentication_error)))
                     }
                 }
    -        }.exhaustive
    +        }
         }
     
         private fun handleStart(action: BootstrapActions.Start) = withState {
    @@ -344,7 +346,7 @@ class BootstrapSharedViewModel @AssistedInject constructor(
                                 )
                             }
                         }
    -                }.exhaustive
    +                }
                 }
             }
         }
    @@ -449,7 +451,7 @@ class BootstrapSharedViewModel @AssistedInject constructor(
                                 }
                             }
                         }
    -                }.exhaustive
    +                }
                 }
             }
         }
    @@ -533,7 +535,7 @@ class BootstrapSharedViewModel @AssistedInject constructor(
                         )
                     }
                 }
    -        }.exhaustive
    +        }
         }
     
         private fun BackupToQuadSMigrationTask.Result.Failure.toHumanReadable(): String {
    diff --git a/vector/src/main/java/im/vector/app/features/crypto/recover/BootstrapWaitingFragment.kt b/vector/src/main/java/im/vector/app/features/crypto/recover/BootstrapWaitingFragment.kt
    index 8cf48a7c66..cc566833d8 100644
    --- a/vector/src/main/java/im/vector/app/features/crypto/recover/BootstrapWaitingFragment.kt
    +++ b/vector/src/main/java/im/vector/app/features/crypto/recover/BootstrapWaitingFragment.kt
    @@ -26,7 +26,7 @@ import im.vector.app.databinding.FragmentBootstrapWaitingBinding
     import javax.inject.Inject
     
     class BootstrapWaitingFragment @Inject constructor() :
    -    VectorBaseFragment() {
    +        VectorBaseFragment() {
     
         override fun getBinding(inflater: LayoutInflater, container: ViewGroup?): FragmentBootstrapWaitingBinding {
             return FragmentBootstrapWaitingBinding.inflate(inflater, container, false)
    diff --git a/vector/src/main/java/im/vector/app/features/crypto/verification/IncomingVerificationRequestHandler.kt b/vector/src/main/java/im/vector/app/features/crypto/verification/IncomingVerificationRequestHandler.kt
    index 3a3f1054f1..2e9fe1bcf9 100644
    --- a/vector/src/main/java/im/vector/app/features/crypto/verification/IncomingVerificationRequestHandler.kt
    +++ b/vector/src/main/java/im/vector/app/features/crypto/verification/IncomingVerificationRequestHandler.kt
    @@ -61,9 +61,9 @@ class IncomingVerificationRequestHandler @Inject constructor(
             // TODO maybe check also if
             val uid = "kvr_${tx.transactionId}"
             when (tx.state) {
    -            is VerificationTxState.OnStarted -> {
    +            is VerificationTxState.OnStarted       -> {
                     // Add a notification for every incoming request
    -                val user = session?.getUser(tx.otherUserId)
    +                val user = session?.userService()?.getUser(tx.otherUserId)
                     val name = user?.toMatrixItem()?.getBestName() ?: tx.otherUserId
                     val alert = VerificationVectorAlert(
                             uid,
    @@ -127,7 +127,7 @@ class IncomingVerificationRequestHandler @Inject constructor(
                     // XXX this is a bit hard coded :/
                     popupAlertManager.cancelAlert("review_login")
                 }
    -            val user = session?.getUser(pr.otherUserId)?.toMatrixItem()
    +            val user = session?.userService()?.getUser(pr.otherUserId)?.toMatrixItem()
                 val name = user?.getBestName() ?: pr.otherUserId
                 val description = if (name == pr.otherUserId) {
                     name
    diff --git a/vector/src/main/java/im/vector/app/features/crypto/verification/VerificationBottomSheet.kt b/vector/src/main/java/im/vector/app/features/crypto/verification/VerificationBottomSheet.kt
    index 65e3fbabba..18a1363d71 100644
    --- a/vector/src/main/java/im/vector/app/features/crypto/verification/VerificationBottomSheet.kt
    +++ b/vector/src/main/java/im/vector/app/features/crypto/verification/VerificationBottomSheet.kt
    @@ -30,7 +30,6 @@ import com.google.android.material.dialog.MaterialAlertDialogBuilder
     import dagger.hilt.android.AndroidEntryPoint
     import im.vector.app.R
     import im.vector.app.core.extensions.commitTransaction
    -import im.vector.app.core.extensions.exhaustive
     import im.vector.app.core.extensions.registerStartForActivityResult
     import im.vector.app.core.extensions.toMvRxBundle
     import im.vector.app.core.platform.VectorBaseActivity
    @@ -49,12 +48,12 @@ import im.vector.app.features.displayname.getBestName
     import im.vector.app.features.home.AvatarRenderer
     import im.vector.app.features.settings.VectorSettingsActivity
     import kotlinx.parcelize.Parcelize
    -import org.matrix.android.sdk.api.crypto.RoomEncryptionTrustLevel
     import org.matrix.android.sdk.api.session.Session
     import org.matrix.android.sdk.api.session.crypto.crosssigning.KEYBACKUP_SECRET_SSSS_NAME
     import org.matrix.android.sdk.api.session.crypto.crosssigning.MASTER_KEY_SSSS_NAME
     import org.matrix.android.sdk.api.session.crypto.crosssigning.SELF_SIGNING_KEY_SSSS_NAME
     import org.matrix.android.sdk.api.session.crypto.crosssigning.USER_SIGNING_KEY_SSSS_NAME
    +import org.matrix.android.sdk.api.session.crypto.model.RoomEncryptionTrustLevel
     import org.matrix.android.sdk.api.session.crypto.verification.CancelCode
     import org.matrix.android.sdk.api.session.crypto.verification.VerificationTxState
     import timber.log.Timber
    @@ -118,7 +117,7 @@ class VerificationBottomSheet : VectorBaseBottomSheetDialogFragment Unit
                 }
     
                 return@withState
    diff --git a/vector/src/main/java/im/vector/app/features/crypto/verification/VerificationBottomSheetViewModel.kt b/vector/src/main/java/im/vector/app/features/crypto/verification/VerificationBottomSheetViewModel.kt
    index 45f7f56957..a7998dc474 100644
    --- a/vector/src/main/java/im/vector/app/features/crypto/verification/VerificationBottomSheetViewModel.kt
    +++ b/vector/src/main/java/im/vector/app/features/crypto/verification/VerificationBottomSheetViewModel.kt
    @@ -28,7 +28,6 @@ import dagger.assisted.AssistedInject
     import im.vector.app.R
     import im.vector.app.core.di.MavericksAssistedViewModelFactory
     import im.vector.app.core.di.hiltMavericksViewModelFactory
    -import im.vector.app.core.extensions.exhaustive
     import im.vector.app.core.platform.VectorViewModel
     import im.vector.app.core.resources.StringProvider
     import kotlinx.coroutines.Dispatchers
    @@ -39,6 +38,11 @@ import org.matrix.android.sdk.api.session.crypto.crosssigning.KEYBACKUP_SECRET_S
     import org.matrix.android.sdk.api.session.crypto.crosssigning.MASTER_KEY_SSSS_NAME
     import org.matrix.android.sdk.api.session.crypto.crosssigning.SELF_SIGNING_KEY_SSSS_NAME
     import org.matrix.android.sdk.api.session.crypto.crosssigning.USER_SIGNING_KEY_SSSS_NAME
    +import org.matrix.android.sdk.api.session.crypto.crosssigning.isVerified
    +import org.matrix.android.sdk.api.session.crypto.keysbackup.KeysBackupLastVersionResult
    +import org.matrix.android.sdk.api.session.crypto.keysbackup.computeRecoveryKey
    +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.crypto.verification.CancelCode
     import org.matrix.android.sdk.api.session.crypto.verification.IncomingSasVerificationTransaction
     import org.matrix.android.sdk.api.session.crypto.verification.PendingVerificationRequest
    @@ -49,14 +53,11 @@ import org.matrix.android.sdk.api.session.crypto.verification.VerificationServic
     import org.matrix.android.sdk.api.session.crypto.verification.VerificationTransaction
     import org.matrix.android.sdk.api.session.crypto.verification.VerificationTxState
     import org.matrix.android.sdk.api.session.events.model.LocalEcho
    +import org.matrix.android.sdk.api.session.getUser
     import org.matrix.android.sdk.api.util.MatrixItem
    +import org.matrix.android.sdk.api.util.awaitCallback
    +import org.matrix.android.sdk.api.util.fromBase64
     import org.matrix.android.sdk.api.util.toMatrixItem
    -import org.matrix.android.sdk.internal.crypto.crosssigning.fromBase64
    -import org.matrix.android.sdk.internal.crypto.crosssigning.isVerified
    -import org.matrix.android.sdk.internal.crypto.keysbackup.model.rest.KeysVersionResult
    -import org.matrix.android.sdk.internal.crypto.keysbackup.util.computeRecoveryKey
    -import org.matrix.android.sdk.internal.crypto.model.ImportRoomKeysResult
    -import org.matrix.android.sdk.internal.util.awaitCallback
     import timber.log.Timber
     
     data class VerificationBottomSheetViewState(
    @@ -149,7 +150,7 @@ class VerificationBottomSheetViewModel @AssistedInject constructor(
                         pendingRequest = if (pr != null) Success(pr) else Uninitialized,
                         isMe = initialState.otherUserId == session.myUserId,
                         currentDeviceCanCrossSign = session.cryptoService().crossSigningService().canCrossSign(),
    -                    quadSContainsSecrets = session.sharedSecretStorageService.isRecoverySetup(),
    +                    quadSContainsSecrets = session.sharedSecretStorageService().isRecoverySetup(),
                         hasAnyOtherSession = hasAnyOtherSession
                 )
             }
    @@ -231,7 +232,7 @@ class VerificationBottomSheetViewModel @AssistedInject constructor(
         override fun handle(action: VerificationAction) = withState { state ->
             val otherUserId = state.otherUserMxItem?.id ?: return@withState
             val roomId = state.roomId
    -                ?: session.getExistingDirectRoomWithUser(otherUserId)
    +                ?: session.roomService().getExistingDirectRoomWithUser(otherUserId)
     
             when (action) {
                 is VerificationAction.RequestVerificationByDM      -> {
    @@ -244,7 +245,7 @@ class VerificationBottomSheetViewModel @AssistedInject constructor(
                             )
                         }
                         viewModelScope.launch {
    -                        val result = runCatching { session.createDirectRoom(otherUserId) }
    +                        val result = runCatching { session.roomService().createDirectRoom(otherUserId) }
                             result.fold(
                                     { data ->
                                         setState {
    @@ -365,14 +366,14 @@ class VerificationBottomSheetViewModel @AssistedInject constructor(
                         copy(verifyingFrom4S = false)
                     }
                 }
    -        }.exhaustive
    +        }
         }
     
         private fun handleSecretBackFromSSSS(action: VerificationAction.GotResultFromSsss) {
             viewModelScope.launch(Dispatchers.IO) {
                 try {
                     action.cypherData.fromBase64().inputStream().use { ins ->
    -                    val res = session.loadSecureSecret>(ins, action.alias)
    +                    val res = session.secureStorageService().loadSecureSecret>(ins, action.alias)
                         val trustResult = session.cryptoService().crossSigningService().checkTrustFromPrivateKeys(
                                 res?.get(MASTER_KEY_SSSS_NAME),
                                 res?.get(USER_SIGNING_KEY_SSSS_NAME),
    @@ -427,9 +428,9 @@ class VerificationBottomSheetViewModel @AssistedInject constructor(
                         Timber.v("## Keybackup secret not restored from SSSS")
                     }
     
    -                val version = awaitCallback {
    +                val version = awaitCallback {
                         session.cryptoService().keysBackupService().getCurrentVersion(it)
    -                } ?: return@launch
    +                }.toKeysVersionResult() ?: return@launch
     
                     awaitCallback {
                         session.cryptoService().keysBackupService().restoreKeysWithRecoveryKey(
    diff --git a/vector/src/main/java/im/vector/app/features/crypto/verification/conclusion/VerificationConclusionController.kt b/vector/src/main/java/im/vector/app/features/crypto/verification/conclusion/VerificationConclusionController.kt
    index 1d6dfbd947..ee77444b2e 100644
    --- a/vector/src/main/java/im/vector/app/features/crypto/verification/conclusion/VerificationConclusionController.kt
    +++ b/vector/src/main/java/im/vector/app/features/crypto/verification/conclusion/VerificationConclusionController.kt
    @@ -26,7 +26,7 @@ import im.vector.app.features.crypto.verification.epoxy.bottomSheetVerificationB
     import im.vector.app.features.crypto.verification.epoxy.bottomSheetVerificationNoticeItem
     import im.vector.app.features.html.EventHtmlRenderer
     import im.vector.lib.core.utils.epoxy.charsequence.toEpoxyCharSequence
    -import org.matrix.android.sdk.api.crypto.RoomEncryptionTrustLevel
    +import org.matrix.android.sdk.api.session.crypto.model.RoomEncryptionTrustLevel
     import javax.inject.Inject
     
     class VerificationConclusionController @Inject constructor(
    diff --git a/vector/src/main/java/im/vector/app/features/crypto/verification/conclusion/VerificationConclusionViewModel.kt b/vector/src/main/java/im/vector/app/features/crypto/verification/conclusion/VerificationConclusionViewModel.kt
    index 82bdbccdb3..0e56a6857b 100644
    --- a/vector/src/main/java/im/vector/app/features/crypto/verification/conclusion/VerificationConclusionViewModel.kt
    +++ b/vector/src/main/java/im/vector/app/features/crypto/verification/conclusion/VerificationConclusionViewModel.kt
    @@ -36,7 +36,7 @@ enum class ConclusionState {
     }
     
     class VerificationConclusionViewModel(initialState: VerificationConclusionViewState) :
    -    VectorViewModel(initialState) {
    +        VectorViewModel(initialState) {
     
         companion object : MavericksViewModelFactory {
     
    diff --git a/vector/src/main/java/im/vector/app/features/crypto/verification/emoji/VerificationEmojiCodeViewModel.kt b/vector/src/main/java/im/vector/app/features/crypto/verification/emoji/VerificationEmojiCodeViewModel.kt
    index 6f213adb7e..4b59e2e6fb 100644
    --- a/vector/src/main/java/im/vector/app/features/crypto/verification/emoji/VerificationEmojiCodeViewModel.kt
    +++ b/vector/src/main/java/im/vector/app/features/crypto/verification/emoji/VerificationEmojiCodeViewModel.kt
    @@ -40,6 +40,7 @@ import org.matrix.android.sdk.api.session.crypto.verification.SasVerificationTra
     import org.matrix.android.sdk.api.session.crypto.verification.VerificationService
     import org.matrix.android.sdk.api.session.crypto.verification.VerificationTransaction
     import org.matrix.android.sdk.api.session.crypto.verification.VerificationTxState
    +import org.matrix.android.sdk.api.session.getUser
     import org.matrix.android.sdk.api.util.MatrixItem
     import org.matrix.android.sdk.api.util.toMatrixItem
     
    @@ -139,6 +140,7 @@ class VerificationEmojiCodeViewModel @AssistedInject constructor(
                         )
                     }
                 }
    +            else                                  -> Unit
             }
         }
     
    diff --git a/vector/src/main/java/im/vector/app/features/crypto/verification/epoxy/BottomSheetVerificationBigImageItem.kt b/vector/src/main/java/im/vector/app/features/crypto/verification/epoxy/BottomSheetVerificationBigImageItem.kt
    index b15f857c3a..adf3e8f7e5 100644
    --- a/vector/src/main/java/im/vector/app/features/crypto/verification/epoxy/BottomSheetVerificationBigImageItem.kt
    +++ b/vector/src/main/java/im/vector/app/features/crypto/verification/epoxy/BottomSheetVerificationBigImageItem.kt
    @@ -22,7 +22,7 @@ import im.vector.app.R
     import im.vector.app.core.epoxy.VectorEpoxyHolder
     import im.vector.app.core.epoxy.VectorEpoxyModel
     import im.vector.app.core.ui.views.ShieldImageView
    -import org.matrix.android.sdk.api.crypto.RoomEncryptionTrustLevel
    +import org.matrix.android.sdk.api.session.crypto.model.RoomEncryptionTrustLevel
     
     /**
      * A action for bottom sheet.
    diff --git a/vector/src/main/java/im/vector/app/features/crypto/verification/qrconfirmation/VerificationQRWaitingController.kt b/vector/src/main/java/im/vector/app/features/crypto/verification/qrconfirmation/VerificationQRWaitingController.kt
    index 8de5f94ec9..1ae0da603a 100644
    --- a/vector/src/main/java/im/vector/app/features/crypto/verification/qrconfirmation/VerificationQRWaitingController.kt
    +++ b/vector/src/main/java/im/vector/app/features/crypto/verification/qrconfirmation/VerificationQRWaitingController.kt
    @@ -24,7 +24,7 @@ import im.vector.app.features.crypto.verification.epoxy.bottomSheetVerificationB
     import im.vector.app.features.crypto.verification.epoxy.bottomSheetVerificationNoticeItem
     import im.vector.app.features.crypto.verification.epoxy.bottomSheetVerificationWaitingItem
     import im.vector.lib.core.utils.epoxy.charsequence.toEpoxyCharSequence
    -import org.matrix.android.sdk.api.crypto.RoomEncryptionTrustLevel
    +import org.matrix.android.sdk.api.session.crypto.model.RoomEncryptionTrustLevel
     import javax.inject.Inject
     
     class VerificationQRWaitingController @Inject constructor(
    diff --git a/vector/src/main/java/im/vector/app/features/crypto/verification/qrconfirmation/VerificationQrScannedByOtherController.kt b/vector/src/main/java/im/vector/app/features/crypto/verification/qrconfirmation/VerificationQrScannedByOtherController.kt
    index 38f29622d0..71d64b99bc 100644
    --- a/vector/src/main/java/im/vector/app/features/crypto/verification/qrconfirmation/VerificationQrScannedByOtherController.kt
    +++ b/vector/src/main/java/im/vector/app/features/crypto/verification/qrconfirmation/VerificationQrScannedByOtherController.kt
    @@ -27,7 +27,7 @@ import im.vector.app.features.crypto.verification.epoxy.bottomSheetVerificationB
     import im.vector.app.features.crypto.verification.epoxy.bottomSheetVerificationNoticeItem
     import im.vector.app.features.displayname.getBestName
     import im.vector.lib.core.utils.epoxy.charsequence.toEpoxyCharSequence
    -import org.matrix.android.sdk.api.crypto.RoomEncryptionTrustLevel
    +import org.matrix.android.sdk.api.session.crypto.model.RoomEncryptionTrustLevel
     import javax.inject.Inject
     
     class VerificationQrScannedByOtherController @Inject constructor(
    diff --git a/vector/src/main/java/im/vector/app/features/crypto/verification/request/VerificationRequestController.kt b/vector/src/main/java/im/vector/app/features/crypto/verification/request/VerificationRequestController.kt
    index 90997830a0..781677433b 100644
    --- a/vector/src/main/java/im/vector/app/features/crypto/verification/request/VerificationRequestController.kt
    +++ b/vector/src/main/java/im/vector/app/features/crypto/verification/request/VerificationRequestController.kt
    @@ -18,6 +18,7 @@ package im.vector.app.features.crypto.verification.request
     
     import androidx.core.text.toSpannable
     import com.airbnb.epoxy.EpoxyController
    +import com.airbnb.mvrx.Fail
     import com.airbnb.mvrx.Loading
     import com.airbnb.mvrx.Success
     import com.airbnb.mvrx.Uninitialized
    @@ -153,6 +154,7 @@ class VerificationRequestController @Inject constructor(
                             }
                         }
                     }
    +                is Fail          -> Unit
                 }
             }
     
    diff --git a/vector/src/main/java/im/vector/app/features/devtools/RoomDevToolActivity.kt b/vector/src/main/java/im/vector/app/features/devtools/RoomDevToolActivity.kt
    index 57d3ccc16b..f1f6142fa2 100644
    --- a/vector/src/main/java/im/vector/app/features/devtools/RoomDevToolActivity.kt
    +++ b/vector/src/main/java/im/vector/app/features/devtools/RoomDevToolActivity.kt
    @@ -34,7 +34,6 @@ import com.airbnb.mvrx.withState
     import com.google.android.material.dialog.MaterialAlertDialogBuilder
     import dagger.hilt.android.AndroidEntryPoint
     import im.vector.app.R
    -import im.vector.app.core.extensions.exhaustive
     import im.vector.app.core.extensions.replaceFragment
     import im.vector.app.core.platform.SimpleFragmentActivity
     import im.vector.app.core.resources.ColorProvider
    @@ -79,7 +78,7 @@ class RoomDevToolActivity : SimpleFragmentActivity(), FragmentManager.OnBackStac
                         Unit
                     }
                     is DevToolsViewEvents.ShowSnackMessage -> showSnackbar(it.message)
    -            }.exhaustive
    +            }
             }
             supportFragmentManager.addOnBackStackChangedListener(this)
         }
    diff --git a/vector/src/main/java/im/vector/app/features/devtools/RoomDevToolEditFragment.kt b/vector/src/main/java/im/vector/app/features/devtools/RoomDevToolEditFragment.kt
    index dd0bd174af..1b6fbb7359 100644
    --- a/vector/src/main/java/im/vector/app/features/devtools/RoomDevToolEditFragment.kt
    +++ b/vector/src/main/java/im/vector/app/features/devtools/RoomDevToolEditFragment.kt
    @@ -32,7 +32,7 @@ import reactivecircus.flowbinding.android.widget.textChanges
     import javax.inject.Inject
     
     class RoomDevToolEditFragment @Inject constructor() :
    -    VectorBaseFragment() {
    +        VectorBaseFragment() {
     
         private val sharedViewModel: RoomDevToolViewModel by activityViewModel()
     
    diff --git a/vector/src/main/java/im/vector/app/features/devtools/RoomDevToolViewModel.kt b/vector/src/main/java/im/vector/app/features/devtools/RoomDevToolViewModel.kt
    index c3524e2cdf..80c1dbeae6 100644
    --- a/vector/src/main/java/im/vector/app/features/devtools/RoomDevToolViewModel.kt
    +++ b/vector/src/main/java/im/vector/app/features/devtools/RoomDevToolViewModel.kt
    @@ -36,10 +36,11 @@ import org.matrix.android.sdk.api.session.Session
     import org.matrix.android.sdk.api.session.events.model.Event
     import org.matrix.android.sdk.api.session.events.model.EventType
     import org.matrix.android.sdk.api.session.events.model.toModel
    +import org.matrix.android.sdk.api.session.getRoom
     import org.matrix.android.sdk.api.session.room.model.message.MessageContent
     import org.matrix.android.sdk.api.util.JsonDict
    +import org.matrix.android.sdk.api.util.MatrixJsonParser
     import org.matrix.android.sdk.flow.flow
    -import org.matrix.android.sdk.internal.di.MoshiProvider
     
     class RoomDevToolViewModel @AssistedInject constructor(
             @Assisted val initialState: RoomDevToolViewState,
    @@ -75,7 +76,7 @@ class RoomDevToolViewModel @AssistedInject constructor(
                     }
                 }
                 is RoomDevToolAction.ShowStateEvent            -> {
    -                val jsonString = MoshiProvider.providesMoshi()
    +                val jsonString = MatrixJsonParser.getMoshi()
                             .adapter(Event::class.java)
                             .toJson(action.event)
     
    @@ -168,7 +169,7 @@ class RoomDevToolViewModel @AssistedInject constructor(
                     val room = session.getRoom(initialState.roomId)
                             ?: throw IllegalArgumentException(stringProvider.getString(R.string.room_error_not_found))
     
    -                val adapter = MoshiProvider.providesMoshi()
    +                val adapter = MatrixJsonParser.getMoshi()
                             .adapter(Types.newParameterizedType(Map::class.java, String::class.java, Any::class.java))
                     val json = adapter.fromJson(state.editedContent ?: "")
                             ?: throw IllegalArgumentException(stringProvider.getString(R.string.dev_tools_error_no_content))
    @@ -202,7 +203,7 @@ class RoomDevToolViewModel @AssistedInject constructor(
                     val room = session.getRoom(initialState.roomId)
                             ?: throw IllegalArgumentException(stringProvider.getString(R.string.room_error_not_found))
     
    -                val adapter = MoshiProvider.providesMoshi()
    +                val adapter = MatrixJsonParser.getMoshi()
                             .adapter(Types.newParameterizedType(Map::class.java, String::class.java, Any::class.java))
                     val json = adapter.fromJson(state.sendEventDraft?.content ?: "")
                             ?: throw IllegalArgumentException(stringProvider.getString(R.string.dev_tools_error_no_content))
    diff --git a/vector/src/main/java/im/vector/app/features/discovery/DiscoverySettingsController.kt b/vector/src/main/java/im/vector/app/features/discovery/DiscoverySettingsController.kt
    index 551b72dd82..e7d74e3d38 100644
    --- a/vector/src/main/java/im/vector/app/features/discovery/DiscoverySettingsController.kt
    +++ b/vector/src/main/java/im/vector/app/features/discovery/DiscoverySettingsController.kt
    @@ -70,6 +70,7 @@ class DiscoverySettingsController @Inject constructor(
                         buildMsisdnSection(data.phoneNumbersList)
                     }
                 }
    +            else       -> Unit
             }
         }
     
    @@ -209,18 +210,19 @@ class DiscoverySettingsController @Inject constructor(
                 titleResId(R.string.settings_discovery_emails_title)
             }
             when (emails) {
    -            is Incomplete -> {
    +            Uninitialized,
    +            is Loading -> {
                     loadingItem {
                         id("emailsLoading")
                     }
                 }
    -            is Fail       -> {
    +            is Fail    -> {
                     settingsInfoItem {
                         id("emailsError")
                         helperText(emails.error.message)
                     }
                 }
    -            is Success    -> {
    +            is Success -> {
                     if (emails().isEmpty()) {
                         settingsInfoItem {
                             id("emailsEmpty")
    @@ -277,18 +279,19 @@ class DiscoverySettingsController @Inject constructor(
             }
     
             when (msisdns) {
    -            is Incomplete -> {
    +            Uninitialized,
    +            is Loading -> {
                     loadingItem {
                         id("msisdnLoading")
                     }
                 }
    -            is Fail       -> {
    +            is Fail    -> {
                     settingsInfoItem {
                         id("msisdnListError")
                         helperText(msisdns.error.message)
                     }
                 }
    -            is Success    -> {
    +            is Success -> {
                     if (msisdns().isEmpty()) {
                         settingsInfoItem {
                             id("no_msisdn")
    @@ -384,7 +387,9 @@ class DiscoverySettingsController @Inject constructor(
                                 else          -> iconMode(IconMode.NONE)
                             }
                         }
    +                    null                            -> Unit
                     }
    +                else       -> Unit
                 }
             }
         }
    diff --git a/vector/src/main/java/im/vector/app/features/discovery/DiscoverySettingsFragment.kt b/vector/src/main/java/im/vector/app/features/discovery/DiscoverySettingsFragment.kt
    index 523e8cb9bb..2de03f296e 100644
    --- a/vector/src/main/java/im/vector/app/features/discovery/DiscoverySettingsFragment.kt
    +++ b/vector/src/main/java/im/vector/app/features/discovery/DiscoverySettingsFragment.kt
    @@ -28,7 +28,6 @@ import com.google.android.material.dialog.MaterialAlertDialogBuilder
     import im.vector.app.R
     import im.vector.app.core.extensions.cleanup
     import im.vector.app.core.extensions.configureWith
    -import im.vector.app.core.extensions.exhaustive
     import im.vector.app.core.extensions.observeEvent
     import im.vector.app.core.extensions.registerStartForActivityResult
     import im.vector.app.core.platform.VectorBaseFragment
    @@ -70,7 +69,7 @@ class DiscoverySettingsFragment @Inject constructor(
                 when (it) {
                     is DiscoverySharedViewModelAction.ChangeIdentityServer ->
                         viewModel.handle(DiscoverySettingsAction.ChangeIdentityServer(it.newUrl))
    -            }.exhaustive
    +            }
             }
     
             viewModel.observeViewEvents {
    @@ -78,7 +77,7 @@ class DiscoverySettingsFragment @Inject constructor(
                     is DiscoverySettingsViewEvents.Failure -> {
                         displayErrorDialog(it.throwable)
                     }
    -            }.exhaustive
    +            }
             }
             if (discoveryArgs.expandIdentityPolicies) {
                 viewModel.handle(DiscoverySettingsAction.SetPoliciesExpandState(expanded = true))
    diff --git a/vector/src/main/java/im/vector/app/features/discovery/DiscoverySettingsViewModel.kt b/vector/src/main/java/im/vector/app/features/discovery/DiscoverySettingsViewModel.kt
    index 19f233fe98..47151223a4 100644
    --- a/vector/src/main/java/im/vector/app/features/discovery/DiscoverySettingsViewModel.kt
    +++ b/vector/src/main/java/im/vector/app/features/discovery/DiscoverySettingsViewModel.kt
    @@ -27,7 +27,6 @@ import dagger.assisted.AssistedInject
     import im.vector.app.R
     import im.vector.app.core.di.MavericksAssistedViewModelFactory
     import im.vector.app.core.di.hiltMavericksViewModelFactory
    -import im.vector.app.core.extensions.exhaustive
     import im.vector.app.core.platform.VectorViewModel
     import im.vector.app.core.resources.StringProvider
     import kotlinx.coroutines.flow.launchIn
    @@ -113,7 +112,7 @@ class DiscoverySettingsViewModel @AssistedInject constructor(
                 is DiscoverySettingsAction.FinalizeBind3pid       -> finalizeBind3pid(action, true)
                 is DiscoverySettingsAction.SubmitMsisdnToken      -> submitMsisdnToken(action)
                 is DiscoverySettingsAction.CancelBinding          -> cancelBinding(action)
    -        }.exhaustive
    +        }
         }
     
         private fun handleUpdateUserConsent(action: DiscoverySettingsAction.UpdateUserConsent) {
    @@ -235,7 +234,7 @@ class DiscoverySettingsViewModel @AssistedInject constructor(
             when (action.threePid) {
                 is ThreePid.Email  -> revokeEmail(action.threePid)
                 is ThreePid.Msisdn -> revokeMsisdn(action.threePid)
    -        }.exhaustive
    +        }
         }
     
         private fun revokeEmail(threePid: ThreePid.Email) = withState { state ->
    @@ -291,7 +290,7 @@ class DiscoverySettingsViewModel @AssistedInject constructor(
         }
     
         private fun retrieveBinding() {
    -        retrieveBinding(session.getThreePids())
    +        retrieveBinding(session.profileService().getThreePids())
         }
     
         private fun retrieveBinding(threePids: List) = withState { state ->
    diff --git a/vector/src/main/java/im/vector/app/features/discovery/Extensions.kt b/vector/src/main/java/im/vector/app/features/discovery/Extensions.kt
    index 24d675695b..dc25a35646 100644
    --- a/vector/src/main/java/im/vector/app/features/discovery/Extensions.kt
    +++ b/vector/src/main/java/im/vector/app/features/discovery/Extensions.kt
    @@ -18,13 +18,13 @@ package im.vector.app.features.discovery
     
     import im.vector.app.core.utils.ensureProtocol
     import org.matrix.android.sdk.api.session.Session
    +import org.matrix.android.sdk.api.session.terms.TermsResponse
     import org.matrix.android.sdk.api.session.terms.TermsService
    -import org.matrix.android.sdk.internal.session.terms.TermsResponse
     
     suspend fun Session.fetchIdentityServerWithTerms(userLanguage: String): ServerAndPolicies? {
         return identityService().getCurrentIdentityServerUrl()
                 ?.let { identityServerUrl ->
    -                val termsResponse = getTerms(TermsService.ServiceType.IdentityService, identityServerUrl.ensureProtocol())
    +                val termsResponse = termsService().getTerms(TermsService.ServiceType.IdentityService, identityServerUrl.ensureProtocol())
                             .serverResponse
                     buildServerAndPolicies(identityServerUrl, termsResponse, userLanguage)
                 }
    @@ -32,7 +32,7 @@ suspend fun Session.fetchIdentityServerWithTerms(userLanguage: String): ServerAn
     
     suspend fun Session.fetchHomeserverWithTerms(userLanguage: String): ServerAndPolicies {
         val homeserverUrl = sessionParams.homeServerUrl
    -    val terms = getHomeserverTerms(homeserverUrl.ensureProtocol())
    +    val terms = termsService().getHomeserverTerms(homeserverUrl.ensureProtocol())
         return buildServerAndPolicies(homeserverUrl, terms, userLanguage)
     }
     
    diff --git a/vector/src/main/java/im/vector/app/features/discovery/SettingsTextButtonSingleLineItem.kt b/vector/src/main/java/im/vector/app/features/discovery/SettingsTextButtonSingleLineItem.kt
    index 527d28dfad..29a44a1d8a 100644
    --- a/vector/src/main/java/im/vector/app/features/discovery/SettingsTextButtonSingleLineItem.kt
    +++ b/vector/src/main/java/im/vector/app/features/discovery/SettingsTextButtonSingleLineItem.kt
    @@ -34,7 +34,6 @@ import im.vector.app.core.epoxy.attributes.ButtonStyle
     import im.vector.app.core.epoxy.attributes.ButtonType
     import im.vector.app.core.epoxy.attributes.IconMode
     import im.vector.app.core.epoxy.onClick
    -import im.vector.app.core.extensions.exhaustive
     import im.vector.app.core.extensions.setTextOrHide
     import im.vector.app.core.resources.ColorProvider
     import im.vector.app.core.resources.StringProvider
    @@ -122,7 +121,7 @@ abstract class SettingsTextButtonSingleLineItem : EpoxyModelWithHolder {
                                 holder.mainButton.setTextColor(colorProvider.getColorFromAttribute(R.attr.colorError))
                             }
    -                    }.exhaustive
    +                    }
                         holder.mainButton.onClick(buttonClickListener)
                     }
                     ButtonType.SWITCH    -> {
    @@ -133,7 +132,7 @@ abstract class SettingsTextButtonSingleLineItem : EpoxyModelWithHolder(initialState) {
    +        VectorViewModel(initialState) {
     
         @AssistedFactory
         interface Factory : MavericksAssistedViewModelFactory {
    @@ -67,7 +66,7 @@ class SetIdentityServerViewModel @AssistedInject constructor(
             when (action) {
                 SetIdentityServerAction.UseDefaultIdentityServer   -> useDefault()
                 is SetIdentityServerAction.UseCustomIdentityServer -> usedCustomIdentityServerUrl(action)
    -        }.exhaustive
    +        }
         }
     
         private fun useDefault() = withState { state ->
    @@ -108,7 +107,7 @@ class SetIdentityServerViewModel @AssistedInject constructor(
     
         private suspend fun checkTerms(baseUrl: String) {
             try {
    -            val data = mxSession.getTerms(TermsService.ServiceType.IdentityService, baseUrl)
    +            val data = mxSession.termsService().getTerms(TermsService.ServiceType.IdentityService, baseUrl)
     
                 // has all been accepted?
                 val resp = data.serverResponse
    diff --git a/vector/src/main/java/im/vector/app/features/home/HomeActivity.kt b/vector/src/main/java/im/vector/app/features/home/HomeActivity.kt
    index 964fb6f365..d0ae7581be 100644
    --- a/vector/src/main/java/im/vector/app/features/home/HomeActivity.kt
    +++ b/vector/src/main/java/im/vector/app/features/home/HomeActivity.kt
    @@ -38,7 +38,6 @@ import dagger.hilt.android.AndroidEntryPoint
     import im.vector.app.AppStateHandler
     import im.vector.app.R
     import im.vector.app.core.di.ActiveSessionHolder
    -import im.vector.app.core.extensions.exhaustive
     import im.vector.app.core.extensions.hideKeyboard
     import im.vector.app.core.extensions.registerStartForActivityResult
     import im.vector.app.core.extensions.replaceFragment
    @@ -80,9 +79,9 @@ import kotlinx.coroutines.launch
     import kotlinx.parcelize.Parcelize
     import org.matrix.android.sdk.api.session.initsync.SyncStatusService
     import org.matrix.android.sdk.api.session.permalinks.PermalinkService
    +import org.matrix.android.sdk.api.session.sync.InitialSyncStrategy
    +import org.matrix.android.sdk.api.session.sync.initialSyncStrategy
     import org.matrix.android.sdk.api.util.MatrixItem
    -import org.matrix.android.sdk.internal.session.sync.InitialSyncStrategy
    -import org.matrix.android.sdk.internal.session.sync.initialSyncStrategy
     import timber.log.Timber
     import javax.inject.Inject
     
    @@ -90,6 +89,7 @@ import javax.inject.Inject
     data class HomeActivityArgs(
             val clearNotification: Boolean,
             val accountCreation: Boolean,
    +        val hasExistingSession: Boolean = false,
             val inviteNotificationRoomId: String? = null
     ) : Parcelable
     
    @@ -106,6 +106,7 @@ class HomeActivity :
     
         @Suppress("UNUSED")
         private val analyticsAccountDataViewModel: AnalyticsAccountDataViewModel by viewModel()
    +
         @Suppress("UNUSED")
         private val userColorAccountDataViewModel: UserColorAccountDataViewModel by viewModel()
     
    @@ -231,7 +232,7 @@ class HomeActivity :
                             HomeActivitySharedAction.SendSpaceFeedBack    -> {
                                 bugReporter.openBugReportScreen(this, ReportType.SPACE_BETA_FEEDBACK)
                             }
    -                    }.exhaustive
    +                    }
                     }
                     .launchIn(lifecycleScope)
     
    @@ -253,7 +254,9 @@ class HomeActivity :
                     HomeActivityViewEvents.PromptToEnableSessionPush        -> handlePromptToEnablePush()
                     is HomeActivityViewEvents.OnCrossSignedInvalidated      -> handleCrossSigningInvalidated(it)
                     HomeActivityViewEvents.ShowAnalyticsOptIn               -> handleShowAnalyticsOptIn()
    -            }.exhaustive
    +                HomeActivityViewEvents.NotifyUserForThreadsMigration    -> handleNotifyUserForThreadsMigration()
    +                is HomeActivityViewEvents.MigrateThreads                -> migrateThreadsIfNeeded(it.checkSession)
    +            }
             }
             homeActivityViewModel.onEach { renderState(it) }
     
    @@ -269,6 +272,48 @@ class HomeActivity :
             navigator.openAnalyticsOptIn(this)
         }
     
    +    /**
    +     * Migrating from old threads io.element.thread to new m.thread needs an initial sync to
    +     * sync and display existing messages appropriately
    +     */
    +    private fun migrateThreadsIfNeeded(checkSession: Boolean) {
    +        if (checkSession) {
    +            // We should check session to ensure we will only clear cache if needed
    +            val args = intent.getParcelableExtra(Mavericks.KEY_ARG)
    +            if (args?.hasExistingSession == true) {
    +                // existingSession --> Will be true only if we came from an existing active session
    +                Timber.i("----> Migrating threads from an existing session..")
    +                handleThreadsMigration()
    +            } else {
    +                // We came from a new session and not an existing one,
    +                // so there is no need to migrate threads while an initial synced performed
    +                Timber.i("----> No thread migration needed, we are ok")
    +                vectorPreferences.setShouldMigrateThreads(shouldMigrate = false)
    +            }
    +        } else {
    +            // Proceed with migration
    +            handleThreadsMigration()
    +        }
    +    }
    +
    +    /**
    +     * Clear cache and restart to invoke an initial sync for threads migration
    +     */
    +    private fun handleThreadsMigration() {
    +        Timber.i("----> Threads Migration detected, clearing cache and sync...")
    +        vectorPreferences.setShouldMigrateThreads(shouldMigrate = false)
    +        MainActivity.restartApp(this, MainActivityArgs(clearCache = true))
    +    }
    +
    +    private fun handleNotifyUserForThreadsMigration() {
    +        MaterialAlertDialogBuilder(this)
    +                .setTitle(R.string.threads_notice_migration_title)
    +                .setMessage(R.string.threads_notice_migration_message)
    +                .setCancelable(true)
    +                .setPositiveButton(R.string.sas_got_it) { _, _ -> }
    +                .show()
    +    }
    +
         private fun handleIntent(intent: Intent?) {
             intent?.dataString?.let { deepLink ->
                 val resolvedLink = when {
    @@ -307,7 +352,7 @@ class HomeActivity :
     
         private fun renderState(state: HomeActivityViewState) {
             when (val status = state.syncStatusServiceStatus) {
    -            is SyncStatusService.Status.Progressing -> {
    +            is SyncStatusService.Status.InitialSyncProgressing -> {
                     val initSyncStepStr = initSyncStepFormatter.format(status.initSyncStep)
                     Timber.v("$initSyncStepStr ${status.percentProgress}")
                     views.waitingView.root.setOnClickListener {
    @@ -325,11 +370,11 @@ class HomeActivity :
                     }
                     views.waitingView.root.isVisible = true
                 }
    -            else                                    -> {
    +            else                                               -> {
                     // Idle or Incremental sync status
                     views.waitingView.root.isVisible = false
                 }
    -        }.exhaustive
    +        }
         }
     
         private fun handleAskPasswordToInitCrossSigning(events: HomeActivityViewEvents.AskPasswordToInitCrossSigning) {
    @@ -535,7 +580,7 @@ class HomeActivity :
         }
     
         override fun spaceInviteBottomSheetOnAccept(spaceId: String) {
    -        navigator.switchToSpace(this, spaceId, Navigator.PostSwitchSpaceAction.None)
    +        navigator.switchToSpace(this, spaceId, Navigator.PostSwitchSpaceAction.OpenRoomList)
         }
     
         override fun spaceInviteBottomSheetOnDecline(spaceId: String) {
    @@ -546,11 +591,13 @@ class HomeActivity :
             fun newIntent(context: Context,
                           clearNotification: Boolean = false,
                           accountCreation: Boolean = false,
    +                      existingSession: Boolean = false,
                           inviteNotificationRoomId: String? = null
             ): Intent {
                 val args = HomeActivityArgs(
                         clearNotification = clearNotification,
                         accountCreation = accountCreation,
    +                    hasExistingSession = existingSession,
                         inviteNotificationRoomId = inviteNotificationRoomId
                 )
     
    @@ -566,6 +613,6 @@ class HomeActivity :
         }
     
         override fun mxToBottomSheetSwitchToSpace(spaceId: String) {
    -        navigator.switchToSpace(this, spaceId, Navigator.PostSwitchSpaceAction.None)
    +        navigator.switchToSpace(this, spaceId, Navigator.PostSwitchSpaceAction.OpenRoomList)
         }
     }
    diff --git a/vector/src/main/java/im/vector/app/features/home/HomeActivityViewEvents.kt b/vector/src/main/java/im/vector/app/features/home/HomeActivityViewEvents.kt
    index adc44a57bd..5efd49a579 100644
    --- a/vector/src/main/java/im/vector/app/features/home/HomeActivityViewEvents.kt
    +++ b/vector/src/main/java/im/vector/app/features/home/HomeActivityViewEvents.kt
    @@ -25,4 +25,6 @@ sealed interface HomeActivityViewEvents : VectorViewEvents {
         data class OnCrossSignedInvalidated(val userItem: MatrixItem.UserItem) : HomeActivityViewEvents
         object PromptToEnableSessionPush : HomeActivityViewEvents
         object ShowAnalyticsOptIn : HomeActivityViewEvents
    +    object NotifyUserForThreadsMigration : HomeActivityViewEvents
    +    data class MigrateThreads(val checkSession: Boolean) : HomeActivityViewEvents
     }
    diff --git a/vector/src/main/java/im/vector/app/features/home/HomeActivityViewModel.kt b/vector/src/main/java/im/vector/app/features/home/HomeActivityViewModel.kt
    index 35c112b63a..f0e27e2ee7 100644
    --- a/vector/src/main/java/im/vector/app/features/home/HomeActivityViewModel.kt
    +++ b/vector/src/main/java/im/vector/app/features/home/HomeActivityViewModel.kt
    @@ -25,7 +25,6 @@ import im.vector.app.config.analyticsConfig
     import im.vector.app.core.di.ActiveSessionHolder
     import im.vector.app.core.di.MavericksAssistedViewModelFactory
     import im.vector.app.core.di.hiltMavericksViewModelFactory
    -import im.vector.app.core.extensions.exhaustive
     import im.vector.app.core.platform.VectorViewModel
     import im.vector.app.features.analytics.store.AnalyticsStore
     import im.vector.app.features.login.ReAuthHelper
    @@ -43,15 +42,17 @@ import org.matrix.android.sdk.api.auth.data.LoginFlowTypes
     import org.matrix.android.sdk.api.auth.registration.RegistrationFlowResponse
     import org.matrix.android.sdk.api.auth.registration.nextUncompletedStage
     import org.matrix.android.sdk.api.extensions.tryOrNull
    -import org.matrix.android.sdk.api.pushrules.RuleIds
    +import org.matrix.android.sdk.api.session.crypto.model.CryptoDeviceInfo
    +import org.matrix.android.sdk.api.session.crypto.model.MXUsersDevicesMap
    +import org.matrix.android.sdk.api.session.getUser
     import org.matrix.android.sdk.api.session.initsync.SyncStatusService
    +import org.matrix.android.sdk.api.session.pushrules.RuleIds
     import org.matrix.android.sdk.api.session.room.model.Membership
     import org.matrix.android.sdk.api.session.room.roomSummaryQueryParams
    +import org.matrix.android.sdk.api.settings.LightweightSettingsStorage
    +import org.matrix.android.sdk.api.util.awaitCallback
     import org.matrix.android.sdk.api.util.toMatrixItem
     import org.matrix.android.sdk.flow.flow
    -import org.matrix.android.sdk.internal.crypto.model.CryptoDeviceInfo
    -import org.matrix.android.sdk.internal.crypto.model.MXUsersDevicesMap
    -import org.matrix.android.sdk.internal.util.awaitCallback
     import timber.log.Timber
     import kotlin.coroutines.Continuation
     import kotlin.coroutines.resume
    @@ -62,6 +63,7 @@ class HomeActivityViewModel @AssistedInject constructor(
             private val activeSessionHolder: ActiveSessionHolder,
             private val reAuthHelper: ReAuthHelper,
             private val analyticsStore: AnalyticsStore,
    +        private val lightweightSettingsStorage: LightweightSettingsStorage,
             private val vectorPreferences: VectorPreferences
     ) : VectorViewModel(initialState) {
     
    @@ -84,6 +86,7 @@ class HomeActivityViewModel @AssistedInject constructor(
             checkSessionPushIsOn()
             observeCrossSigningReset()
             observeAnalytics()
    +        initThreadsMigration()
         }
     
         private fun observeAnalytics() {
    @@ -130,24 +133,64 @@ class HomeActivityViewModel @AssistedInject constructor(
                     .launchIn(viewModelScope)
         }
     
    +    /**
    +     * Handle threads migration. The migration includes:
    +     * - Notify users that had io.element.thread enabled from labs
    +     * - Re-Enable m.thread to those users (that they had enabled labs threads)
    +     * - Handle migration when threads are enabled by default
    +     */
    +    private fun initThreadsMigration() {
    +        // When we would like to enable threads for all users
    +//        if(vectorPreferences.shouldMigrateThreads()) {
    +//            vectorPreferences.setThreadMessagesEnabled()
    +//            lightweightSettingsStorage.setThreadMessagesEnabled(vectorPreferences.areThreadMessagesEnabled())
    +//        }
    +
    +        when {
    +            // Notify users
    +            vectorPreferences.shouldNotifyUserAboutThreads() && vectorPreferences.areThreadMessagesEnabled() -> {
    +                Timber.i("----> Notify users about threads")
    +                // Notify the user if needed that we migrated to support m.thread
    +                // instead of io.element.thread so old thread messages will be displayed as normal timeline messages
    +                _viewEvents.post(HomeActivityViewEvents.NotifyUserForThreadsMigration)
    +                vectorPreferences.userNotifiedAboutThreads()
    +            }
    +            // Migrate users with enabled lab settings
    +            vectorPreferences.shouldNotifyUserAboutThreads() && vectorPreferences.shouldMigrateThreads()     -> {
    +                Timber.i("----> Migrate threads with enabled labs")
    +                // If user had io.element.thread enabled then enable the new thread support,
    +                // clear cache to sync messages appropriately
    +                vectorPreferences.setThreadMessagesEnabled()
    +                lightweightSettingsStorage.setThreadMessagesEnabled(vectorPreferences.areThreadMessagesEnabled())
    +                // Clear Cache
    +                _viewEvents.post(HomeActivityViewEvents.MigrateThreads(checkSession = false))
    +            }
    +            // Enable all users
    +            vectorPreferences.shouldMigrateThreads() && vectorPreferences.areThreadMessagesEnabled()         -> {
    +                Timber.i("----> Try to migrate threads")
    +                _viewEvents.post(HomeActivityViewEvents.MigrateThreads(checkSession = true))
    +            }
    +        }
    +    }
    +
         private fun observeInitialSync() {
             val session = activeSessionHolder.getSafeActiveSession() ?: return
     
    -        session.getSyncStatusLive()
    +        session.syncStatusService().getSyncStatusLive()
                     .asFlow()
                     .onEach { status ->
                         when (status) {
    -                        is SyncStatusService.Status.Progressing -> {
    +                        is SyncStatusService.Status.InitialSyncProgressing -> {
                                 // Schedule a check of the bootstrap when the init sync will be finished
                                 checkBootstrap = true
                             }
    -                        is SyncStatusService.Status.Idle        -> {
    +                        is SyncStatusService.Status.Idle                   -> {
                                 if (checkBootstrap) {
                                     checkBootstrap = false
                                     maybeBootstrapCrossSigningAfterInitialSync()
                                 }
                             }
    -                        else                                    -> Unit
    +                        else                                               -> Unit
                         }
     
                         setState {
    @@ -173,13 +216,16 @@ class HomeActivityViewModel @AssistedInject constructor(
                 if (!vectorPreferences.areNotificationEnabledForDevice()) {
                     // Check if set at account level
                     val mRuleMaster = activeSessionHolder.getSafeActiveSession()
    +                        ?.pushRuleService()
                             ?.getPushRules()
                             ?.getAllRules()
                             ?.find { it.ruleId == RuleIds.RULE_ID_DISABLE_ALL }
                     if (mRuleMaster?.enabled == false) {
                         // So push are enabled at account level but not for this session
                         // Let's check that there are some rooms?
    -                    val knownRooms = activeSessionHolder.getSafeActiveSession()?.getRoomSummaries(roomSummaryQueryParams {
    +                    val knownRooms = activeSessionHolder.getSafeActiveSession()
    +                            ?.roomService()
    +                            ?.getRoomSummaries(roomSummaryQueryParams {
                             memberships = Membership.activeMemberships()
                         })?.size ?: 0
     
    @@ -263,6 +309,6 @@ class HomeActivityViewModel @AssistedInject constructor(
                 HomeActivityViewActions.ViewStarted               -> {
                     initialize()
                 }
    -        }.exhaustive
    +        }
         }
     }
    diff --git a/vector/src/main/java/im/vector/app/features/home/HomeDetailFragment.kt b/vector/src/main/java/im/vector/app/features/home/HomeDetailFragment.kt
    index b052758201..f3973e5d4c 100644
    --- a/vector/src/main/java/im/vector/app/features/home/HomeDetailFragment.kt
    +++ b/vector/src/main/java/im/vector/app/features/home/HomeDetailFragment.kt
    @@ -55,9 +55,9 @@ import im.vector.app.features.settings.VectorSettingsActivity.Companion.EXTRA_DI
     import im.vector.app.features.themes.ThemeUtils
     import im.vector.app.features.workers.signout.BannerState
     import im.vector.app.features.workers.signout.ServerBackupStatusViewModel
    +import org.matrix.android.sdk.api.session.crypto.model.DeviceInfo
     import org.matrix.android.sdk.api.session.group.model.GroupSummary
     import org.matrix.android.sdk.api.session.room.model.RoomSummary
    -import org.matrix.android.sdk.internal.crypto.model.rest.DeviceInfo
     import javax.inject.Inject
     
     class HomeDetailFragment @Inject constructor(
    diff --git a/vector/src/main/java/im/vector/app/features/home/HomeDetailViewModel.kt b/vector/src/main/java/im/vector/app/features/home/HomeDetailViewModel.kt
    index e812942996..fc39165a7e 100644
    --- a/vector/src/main/java/im/vector/app/features/home/HomeDetailViewModel.kt
    +++ b/vector/src/main/java/im/vector/app/features/home/HomeDetailViewModel.kt
    @@ -48,6 +48,7 @@ import kotlinx.coroutines.launch
     import org.matrix.android.sdk.api.query.ActiveSpaceFilter
     import org.matrix.android.sdk.api.query.RoomCategoryFilter
     import org.matrix.android.sdk.api.session.Session
    +import org.matrix.android.sdk.api.session.crypto.NewSessionListener
     import org.matrix.android.sdk.api.session.initsync.SyncStatusService
     import org.matrix.android.sdk.api.session.room.RoomSortOrder
     import org.matrix.android.sdk.api.session.room.model.Membership
    @@ -88,9 +89,16 @@ class HomeDetailViewModel @AssistedInject constructor(
             }
         }
     
    +    private val refreshRoomSummariesOnCryptoSessionChange = object : NewSessionListener {
    +        override fun onNewSession(roomId: String?, senderKey: String, sessionId: String) {
    +            session.roomService().refreshJoinedRoomSummaryPreviews(roomId)
    +        }
    +    }
    +
         init {
             observeSyncState()
             observeRoomGroupingMethod()
    +        session.cryptoService().addNewSessionListener(refreshRoomSummariesOnCryptoSessionChange)
             observeRoomSummaries()
             updatePstnSupportFlag()
             observeDataStore()
    @@ -150,6 +158,7 @@ class HomeDetailViewModel @AssistedInject constructor(
         override fun onCleared() {
             super.onCleared()
             callManager.removeProtocolsCheckerListener(this)
    +        session.cryptoService().removeSessionListener(refreshRoomSummariesOnCryptoSessionChange)
         }
     
         override fun onPSTNSupportUpdated() {
    @@ -167,7 +176,7 @@ class HomeDetailViewModel @AssistedInject constructor(
         private fun handleMarkAllRoomsRead() = withState { _ ->
             // questionable to use viewmodelscope
             viewModelScope.launch(Dispatchers.Default) {
    -            val roomIds = session.getRoomSummaries(
    +            val roomIds = session.roomService().getRoomSummaries(
                         roomSummaryQueryParams {
                             memberships = listOf(Membership.JOIN)
                             roomCategoryFilter = RoomCategoryFilter.ONLY_WITH_NOTIFICATIONS
    @@ -175,7 +184,7 @@ class HomeDetailViewModel @AssistedInject constructor(
                 )
                         .map { it.roomId }
                 try {
    -                session.markAllAsRead(roomIds)
    +                session.roomService().markAllAsRead(roomIds)
                 } catch (failure: Throwable) {
                     Timber.d(failure, "Failed to mark all as read")
                 }
    @@ -189,7 +198,7 @@ class HomeDetailViewModel @AssistedInject constructor(
                         copy(syncState = syncState)
                     }
     
    -        session.getSyncStatusLive()
    +        session.syncStatusService().getSyncStatusLive()
                     .asFlow()
                     .filterIsInstance()
                     .setOnEach {
    @@ -210,7 +219,7 @@ class HomeDetailViewModel @AssistedInject constructor(
             appStateHandler.selectedRoomGroupingFlow.distinctUntilChanged().flatMapLatest {
                 // we use it as a trigger to all changes in room, but do not really load
                 // the actual models
    -            session.getPagedRoomSummariesLive(
    +            session.roomService().getPagedRoomSummariesLive(
                         roomSummaryQueryParams {
                             memberships = Membership.activeMemberships()
                         },
    @@ -228,7 +237,7 @@ class HomeDetailViewModel @AssistedInject constructor(
                                 var dmInvites = 0
                                 var roomsInvite = 0
                                 if (autoAcceptInvites.showInvites()) {
    -                                dmInvites = session.getRoomSummaries(
    +                                dmInvites = session.roomService().getRoomSummaries(
                                             roomSummaryQueryParams {
                                                 memberships = listOf(Membership.INVITE)
                                                 roomCategoryFilter = RoomCategoryFilter.ONLY_DM
    @@ -236,7 +245,7 @@ class HomeDetailViewModel @AssistedInject constructor(
                                             }
                                     ).size
     
    -                                roomsInvite = session.getRoomSummaries(
    +                                roomsInvite = session.roomService().getRoomSummaries(
                                             roomSummaryQueryParams {
                                                 memberships = listOf(Membership.INVITE)
                                                 roomCategoryFilter = RoomCategoryFilter.ONLY_ROOMS
    @@ -245,7 +254,7 @@ class HomeDetailViewModel @AssistedInject constructor(
                                     ).size
                                 }
     
    -                            val dmRooms = session.getNotificationCountForRooms(
    +                            val dmRooms = session.roomService().getNotificationCountForRooms(
                                         roomSummaryQueryParams {
                                             memberships = listOf(Membership.JOIN)
                                             roomCategoryFilter = RoomCategoryFilter.ONLY_DM
    @@ -253,7 +262,7 @@ class HomeDetailViewModel @AssistedInject constructor(
                                         }
                                 )
     
    -                            val otherRooms = session.getNotificationCountForRooms(
    +                            val otherRooms = session.roomService().getNotificationCountForRooms(
                                         roomSummaryQueryParams {
                                             memberships = listOf(Membership.JOIN)
                                             roomCategoryFilter = RoomCategoryFilter.ONLY_ROOMS
    @@ -273,6 +282,7 @@ class HomeDetailViewModel @AssistedInject constructor(
                                     )
                                 }
                             }
    +                        null                                -> Unit
                         }
                     }
                     .launchIn(viewModelScope)
    diff --git a/vector/src/main/java/im/vector/app/features/home/HomeDrawerFragment.kt b/vector/src/main/java/im/vector/app/features/home/HomeDrawerFragment.kt
    index 1aee0257f4..9ce950ba31 100644
    --- a/vector/src/main/java/im/vector/app/features/home/HomeDrawerFragment.kt
    +++ b/vector/src/main/java/im/vector/app/features/home/HomeDrawerFragment.kt
    @@ -60,7 +60,7 @@ class HomeDrawerFragment @Inject constructor(
             if (savedInstanceState == null) {
                 replaceChildFragment(R.id.homeDrawerGroupListContainer, SpaceListFragment::class.java)
             }
    -        session.getUserLive(session.myUserId).observeK(viewLifecycleOwner) { optionalUser ->
    +        session.userService().getUserLive(session.myUserId).observeK(viewLifecycleOwner) { optionalUser ->
                 val user = optionalUser?.getOrNull()
                 if (user != null) {
                     avatarRenderer.render(user.toMatrixItem(), views.homeDrawerHeaderAvatarView)
    diff --git a/vector/src/main/java/im/vector/app/features/home/UnknownDeviceDetectorSharedViewModel.kt b/vector/src/main/java/im/vector/app/features/home/UnknownDeviceDetectorSharedViewModel.kt
    index 8a36a4c19e..3ca19b39f9 100644
    --- a/vector/src/main/java/im/vector/app/features/home/UnknownDeviceDetectorSharedViewModel.kt
    +++ b/vector/src/main/java/im/vector/app/features/home/UnknownDeviceDetectorSharedViewModel.kt
    @@ -38,10 +38,11 @@ import kotlinx.coroutines.flow.sample
     import org.matrix.android.sdk.api.NoOpMatrixCallback
     import org.matrix.android.sdk.api.extensions.orFalse
     import org.matrix.android.sdk.api.session.Session
    +import org.matrix.android.sdk.api.session.crypto.model.DeviceInfo
    +import org.matrix.android.sdk.api.session.getUser
     import org.matrix.android.sdk.api.util.MatrixItem
     import org.matrix.android.sdk.api.util.toMatrixItem
     import org.matrix.android.sdk.flow.flow
    -import org.matrix.android.sdk.internal.crypto.model.rest.DeviceInfo
     import timber.log.Timber
     
     data class UnknownDevicesState(
    @@ -58,7 +59,7 @@ data class DeviceDetectionInfo(
     class UnknownDeviceDetectorSharedViewModel @AssistedInject constructor(@Assisted initialState: UnknownDevicesState,
                                                                            session: Session,
                                                                            private val vectorPreferences: VectorPreferences) :
    -    VectorViewModel(initialState) {
    +        VectorViewModel(initialState) {
     
         sealed class Action : VectorViewModelAction {
             data class IgnoreDevice(val deviceIds: List) : Action()
    diff --git a/vector/src/main/java/im/vector/app/features/home/UnreadMessagesSharedViewModel.kt b/vector/src/main/java/im/vector/app/features/home/UnreadMessagesSharedViewModel.kt
    index 409eb0b845..c1b3937fee 100644
    --- a/vector/src/main/java/im/vector/app/features/home/UnreadMessagesSharedViewModel.kt
    +++ b/vector/src/main/java/im/vector/app/features/home/UnreadMessagesSharedViewModel.kt
    @@ -71,9 +71,11 @@ class UnreadMessagesSharedViewModel @AssistedInject constructor(@Assisted initia
     
         override fun handle(action: EmptyAction) {}
     
    +    private val roomService = session.roomService()
    +
         init {
     
    -        session.getPagedRoomSummariesLive(
    +        roomService.getPagedRoomSummariesLive(
                     roomSummaryQueryParams {
                         this.memberships = listOf(Membership.JOIN)
                         this.activeSpaceFilter = ActiveSpaceFilter.ActiveSpace(null)
    @@ -81,7 +83,7 @@ class UnreadMessagesSharedViewModel @AssistedInject constructor(@Assisted initia
             ).asFlow()
                     .throttleFirst(300)
                     .execute {
    -                    val counts = session.getNotificationCountForRooms(
    +                    val counts = roomService.getNotificationCountForRooms(
                                 roomSummaryQueryParams {
                                     this.memberships = listOf(Membership.JOIN)
                                     this.activeSpaceFilter = ActiveSpaceFilter.ActiveSpace(null)
    @@ -90,7 +92,7 @@ class UnreadMessagesSharedViewModel @AssistedInject constructor(@Assisted initia
                         val invites = if (autoAcceptInvites.hideInvites) {
                             0
                         } else {
    -                        session.getRoomSummaries(
    +                        roomService.getRoomSummaries(
                                     roomSummaryQueryParams {
                                         this.memberships = listOf(Membership.INVITE)
                                         this.activeSpaceFilter = ActiveSpaceFilter.ActiveSpace(null)
    @@ -109,7 +111,7 @@ class UnreadMessagesSharedViewModel @AssistedInject constructor(@Assisted initia
             combine(
                     appStateHandler.selectedRoomGroupingFlow.distinctUntilChanged(),
                     appStateHandler.selectedRoomGroupingFlow.flatMapLatest {
    -                    session.getPagedRoomSummariesLive(
    +                    roomService.getPagedRoomSummariesLive(
                                 roomSummaryQueryParams {
                                     this.memberships = Membership.activeMemberships()
                                 }, sortOrder = RoomSortOrder.NONE
    @@ -131,7 +133,7 @@ class UnreadMessagesSharedViewModel @AssistedInject constructor(@Assisted initia
                         val inviteCount = if (autoAcceptInvites.hideInvites) {
                             0
                         } else {
    -                        session.getRoomSummaries(
    +                        roomService.getRoomSummaries(
                                     roomSummaryQueryParams { this.memberships = listOf(Membership.INVITE) }
                             ).size
                         }
    @@ -139,14 +141,14 @@ class UnreadMessagesSharedViewModel @AssistedInject constructor(@Assisted initia
                         val spaceInviteCount = if (autoAcceptInvites.hideInvites) {
                             0
                         } else {
    -                        session.getRoomSummaries(
    +                        roomService.getRoomSummaries(
                                     spaceSummaryQueryParams {
                                         this.memberships = listOf(Membership.INVITE)
                                     }
                             ).size
                         }
     
    -                    val totalCount = session.getNotificationCountForRooms(
    +                    val totalCount = roomService.getNotificationCountForRooms(
                                 roomSummaryQueryParams {
                                     this.memberships = listOf(Membership.JOIN)
                                     this.activeSpaceFilter = ActiveSpaceFilter.ActiveSpace(null).takeIf {
    diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/JoinReplacementRoomBottomSheet.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/JoinReplacementRoomBottomSheet.kt
    index 99843084ec..ce2903a6fa 100644
    --- a/vector/src/main/java/im/vector/app/features/home/room/detail/JoinReplacementRoomBottomSheet.kt
    +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/JoinReplacementRoomBottomSheet.kt
    @@ -62,15 +62,15 @@ class JoinReplacementRoomBottomSheet :
                 when (joinState) {
                     // it should never be Uninitialized
                     Uninitialized,
    -                is Loading    -> {
    +                is Loading -> {
                         views.roomUpgradeButton.render(ButtonStateView.State.Loading)
                         views.descriptionText.setText(R.string.it_may_take_some_time)
                     }
    -                is Success    -> {
    +                is Success -> {
                         views.roomUpgradeButton.render(ButtonStateView.State.Loaded)
                         dismiss()
                     }
    -                is Fail       -> {
    +                is Fail    -> {
                         // display the error message
                         views.descriptionText.text = errorFormatter.toHumanReadable(joinState.error)
                         views.roomUpgradeButton.render(ButtonStateView.State.Error)
    diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailAction.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailAction.kt
    index d10b363519..f6ea8b76ef 100644
    --- a/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailAction.kt
    +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailAction.kt
    @@ -111,4 +111,7 @@ sealed class RoomDetailAction : VectorViewModelAction {
     
         // Poll
         data class EndPoll(val eventId: String) : RoomDetailAction()
    +
    +    // Live Location
    +    object StopLiveLocationSharing : RoomDetailAction()
     }
    diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailActivity.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailActivity.kt
    index aa4ee825dc..5784e6e264 100644
    --- a/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailActivity.kt
    +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailActivity.kt
    @@ -38,7 +38,7 @@ import im.vector.app.databinding.ActivityRoomDetailBinding
     import im.vector.app.features.analytics.plan.MobileScreen
     import im.vector.app.features.home.room.breadcrumbs.BreadcrumbsFragment
     import im.vector.app.features.home.room.detail.arguments.TimelineArgs
    -import im.vector.app.features.home.room.detail.timeline.helper.VoiceMessagePlaybackTracker
    +import im.vector.app.features.home.room.detail.timeline.helper.AudioMessagePlaybackTracker
     import im.vector.app.features.matrixto.MatrixToBottomSheet
     import im.vector.app.features.navigation.Navigator
     import im.vector.app.features.room.RequireActiveMembershipAction
    @@ -75,7 +75,7 @@ class RoomDetailActivity :
         }
     
         private var lastKnownPlayingOrRecordingState: Boolean? = null
    -    private val playbackActivityListener = VoiceMessagePlaybackTracker.ActivityListener { isPlayingOrRecording ->
    +    private val playbackActivityListener = AudioMessagePlaybackTracker.ActivityListener { isPlayingOrRecording ->
             if (lastKnownPlayingOrRecordingState == isPlayingOrRecording) return@ActivityListener
             when (isPlayingOrRecording) {
                 true  -> keepScreenOn()
    @@ -86,7 +86,7 @@ class RoomDetailActivity :
     
         override fun getCoordinatorLayout() = views.coordinatorLayout
     
    -    @Inject lateinit var playbackTracker: VoiceMessagePlaybackTracker
    +    @Inject lateinit var playbackTracker: AudioMessagePlaybackTracker
         private lateinit var sharedActionViewModel: RoomDetailSharedActionViewModel
         private val requireActiveMembershipViewModel: RequireActiveMembershipViewModel by viewModel()
     
    @@ -152,7 +152,7 @@ class RoomDetailActivity :
         override fun onDestroy() {
             supportFragmentManager.unregisterFragmentLifecycleCallbacks(fragmentLifecycleCallbacks)
             views.drawerLayout.removeDrawerListener(drawerListener)
    -        playbackTracker.unTrackActivity(playbackActivityListener)
    +        playbackTracker.untrackActivity(playbackActivityListener)
             super.onDestroy()
         }
     
    diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailViewEvents.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailViewEvents.kt
    index d08a27324c..f36a1141b8 100644
    --- a/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailViewEvents.kt
    +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailViewEvents.kt
    @@ -20,9 +20,9 @@ import android.net.Uri
     import android.view.View
     import im.vector.app.core.platform.VectorViewEvents
     import im.vector.app.features.call.webrtc.WebRtcCall
    +import org.matrix.android.sdk.api.session.events.model.content.WithHeldCode
     import org.matrix.android.sdk.api.session.widgets.model.Widget
     import org.matrix.android.sdk.api.util.MatrixItem
    -import org.matrix.android.sdk.internal.crypto.model.event.WithHeldCode
     import java.io.File
     
     /**
    @@ -82,4 +82,6 @@ sealed class RoomDetailViewEvents : VectorViewEvents {
         data class StartChatEffect(val type: ChatEffect) : RoomDetailViewEvents()
         object StopChatEffects : RoomDetailViewEvents()
         object RoomReplacementStarted : RoomDetailViewEvents()
    +
    +    data class ChangeLocationIndicator(val isVisible: Boolean) : RoomDetailViewEvents()
     }
    diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailViewState.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailViewState.kt
    index e2b97b0900..0c56d7e7eb 100644
    --- a/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailViewState.kt
    +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailViewState.kt
    @@ -36,7 +36,7 @@ sealed class UnreadState {
         object Unknown : UnreadState()
         object HasNoUnread : UnreadState()
         data class ReadMarkerNotLoaded(val readMarkerId: String) : UnreadState()
    -    data class HasUnread(val firstUnreadEventId: String) : UnreadState()
    +    data class HasUnread(val firstUnreadEventId: String, val readMarkerId: String) : UnreadState()
     }
     
     data class JitsiState(
    @@ -87,7 +87,7 @@ data class RoomDetailViewState(
                 rootThreadEventId = args.threadTimelineArgs?.rootThreadEventId
         )
     
    -    fun isWebRTCCallOptionAvailable() = (asyncRoomSummary.invoke()?.joinedMembersCount ?: 0) <= 2
    +    fun isCallOptionAvailable() = asyncRoomSummary.invoke()?.isDirect ?: true
     
         fun isSearchAvailable() = asyncRoomSummary()?.isEncrypted == false
     
    diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/TimelineFragment.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/TimelineFragment.kt
    index 1c06d0ef58..4603793bd5 100644
    --- a/vector/src/main/java/im/vector/app/features/home/room/detail/TimelineFragment.kt
    +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/TimelineFragment.kt
    @@ -25,6 +25,7 @@ import android.os.Build
     import android.os.Bundle
     import android.text.Spannable
     import android.text.format.DateUtils
    +import android.text.method.LinkMovementMethod
     import android.view.HapticFeedbackConstants
     import android.view.KeyEvent
     import android.view.LayoutInflater
    @@ -40,6 +41,7 @@ import android.widget.TextView
     import android.widget.Toast
     import androidx.annotation.DrawableRes
     import androidx.annotation.StringRes
    +import androidx.appcompat.view.menu.MenuBuilder
     import androidx.core.content.ContextCompat
     import androidx.core.graphics.drawable.DrawableCompat
     import androidx.core.net.toUri
    @@ -72,7 +74,6 @@ import im.vector.app.core.dialogs.ConfirmationDialogBuilder
     import im.vector.app.core.dialogs.GalleryOrCameraDialogHelper
     import im.vector.app.core.epoxy.LayoutManagerStateRestorer
     import im.vector.app.core.extensions.cleanup
    -import im.vector.app.core.extensions.exhaustive
     import im.vector.app.core.extensions.hideKeyboard
     import im.vector.app.core.extensions.registerStartForActivityResult
     import im.vector.app.core.extensions.setTextOrHide
    @@ -135,6 +136,7 @@ import im.vector.app.features.call.conference.ConferenceEventObserver
     import im.vector.app.features.call.conference.JitsiCallViewModel
     import im.vector.app.features.call.webrtc.WebRtcCallManager
     import im.vector.app.features.command.Command
    +import im.vector.app.features.command.ParsedCommand
     import im.vector.app.features.crypto.keysbackup.restore.KeysBackupRestoreActivity
     import im.vector.app.features.crypto.verification.VerificationBottomSheet
     import im.vector.app.features.home.AvatarRenderer
    @@ -155,10 +157,11 @@ import im.vector.app.features.home.room.detail.timeline.action.EventSharedAction
     import im.vector.app.features.home.room.detail.timeline.action.MessageActionsBottomSheet
     import im.vector.app.features.home.room.detail.timeline.action.MessageSharedActionViewModel
     import im.vector.app.features.home.room.detail.timeline.edithistory.ViewEditHistoryBottomSheet
    +import im.vector.app.features.home.room.detail.timeline.helper.AudioMessagePlaybackTracker
     import im.vector.app.features.home.room.detail.timeline.helper.MatrixItemColorProvider
    -import im.vector.app.features.home.room.detail.timeline.helper.VoiceMessagePlaybackTracker
     import im.vector.app.features.home.room.detail.timeline.image.buildImageContentRendererData
     import im.vector.app.features.home.room.detail.timeline.item.AbsMessageItem
    +import im.vector.app.features.home.room.detail.timeline.item.MessageAudioItem
     import im.vector.app.features.home.room.detail.timeline.item.MessageFileItem
     import im.vector.app.features.home.room.detail.timeline.item.MessageImageVideoItem
     import im.vector.app.features.home.room.detail.timeline.item.MessageInformationData
    @@ -170,6 +173,7 @@ import im.vector.app.features.home.room.detail.timeline.url.PreviewUrlRetriever
     import im.vector.app.features.home.room.detail.upgrade.MigrateRoomBottomSheet
     import im.vector.app.features.home.room.detail.views.RoomDetailLazyLoadedViews
     import im.vector.app.features.home.room.detail.widget.RoomWidgetsBottomSheet
    +import im.vector.app.features.home.room.threads.ThreadsManager
     import im.vector.app.features.home.room.threads.arguments.ThreadTimelineArgs
     import im.vector.app.features.html.EventHtmlRenderer
     import im.vector.app.features.html.PillImageSpan
    @@ -177,13 +181,14 @@ import im.vector.app.features.html.PillsPostProcessor
     import im.vector.app.features.invite.VectorInviteView
     import im.vector.app.features.location.LocationSharingMode
     import im.vector.app.features.location.toLocationData
    +import im.vector.app.features.media.AttachmentData
     import im.vector.app.features.media.ImageContentRenderer
     import im.vector.app.features.media.VideoContentRenderer
     import im.vector.app.features.notifications.NotificationDrawerManager
     import im.vector.app.features.notifications.NotificationUtils
     import im.vector.app.features.permalink.NavigationInterceptor
     import im.vector.app.features.permalink.PermalinkHandler
    -import im.vector.app.features.poll.create.PollMode
    +import im.vector.app.features.poll.PollMode
     import im.vector.app.features.reactions.EmojiReactionPickerActivity
     import im.vector.app.features.roomprofile.RoomProfileActivity
     import im.vector.app.features.session.coroutineScope
    @@ -206,10 +211,11 @@ import kotlinx.coroutines.launch
     import kotlinx.coroutines.withContext
     import org.billcarsonfr.jsonviewer.JSonViewerDialog
     import org.commonmark.parser.Parser
    -import org.matrix.android.sdk.api.MatrixConfiguration
     import org.matrix.android.sdk.api.session.Session
     import org.matrix.android.sdk.api.session.content.ContentAttachmentData
     import org.matrix.android.sdk.api.session.events.model.EventType
    +import org.matrix.android.sdk.api.session.events.model.content.EncryptedEventContent
    +import org.matrix.android.sdk.api.session.events.model.content.WithHeldCode
     import org.matrix.android.sdk.api.session.events.model.toModel
     import org.matrix.android.sdk.api.session.room.model.Membership
     import org.matrix.android.sdk.api.session.room.model.RoomSummary
    @@ -233,8 +239,6 @@ import org.matrix.android.sdk.api.session.widgets.model.WidgetType
     import org.matrix.android.sdk.api.util.MatrixItem
     import org.matrix.android.sdk.api.util.MimeTypes
     import org.matrix.android.sdk.api.util.toMatrixItem
    -import org.matrix.android.sdk.internal.crypto.model.event.EncryptedEventContent
    -import org.matrix.android.sdk.internal.crypto.model.event.WithHeldCode
     import reactivecircus.flowbinding.android.view.focusChanges
     import reactivecircus.flowbinding.android.widget.textChanges
     import timber.log.Timber
    @@ -251,6 +255,7 @@ class TimelineFragment @Inject constructor(
             private val notificationDrawerManager: NotificationDrawerManager,
             private val eventHtmlRenderer: EventHtmlRenderer,
             private val vectorPreferences: VectorPreferences,
    +        private val threadsManager: ThreadsManager,
             private val colorProvider: ColorProvider,
             private val dimensionConverter: DimensionConverter,
             private val userPreferencesProvider: UserPreferencesProvider,
    @@ -260,9 +265,8 @@ class TimelineFragment @Inject constructor(
             private val roomDetailPendingActionStore: RoomDetailPendingActionStore,
             private val pillsPostProcessorFactory: PillsPostProcessor.Factory,
             private val callManager: WebRtcCallManager,
    -        private val voiceMessagePlaybackTracker: VoiceMessagePlaybackTracker,
    -        private val clock: Clock,
    -        private val matrixConfiguration: MatrixConfiguration
    +        private val audioMessagePlaybackTracker: AudioMessagePlaybackTracker,
    +        private val clock: Clock
     ) :
             VectorBaseFragment(),
             TimelineEventController.Callback,
    @@ -382,6 +386,7 @@ class TimelineFragment @Inject constructor(
             setupEmojiButton()
             setupRemoveJitsiWidgetView()
             setupVoiceMessageView()
    +        setupLiveLocationIndicator()
     
             views.includeRoomToolbar.roomToolbarContentView.debouncedClicks {
                 navigator.openRoomProfile(requireActivity(), timelineArgs.roomId)
    @@ -434,6 +439,7 @@ class TimelineFragment @Inject constructor(
             messageComposerViewModel.observeViewEvents {
                 when (it) {
                     is MessageComposerViewEvents.JoinRoomCommandSuccess          -> handleJoinedToAnotherRoom(it)
    +                is MessageComposerViewEvents.SlashCommandConfirmationRequest -> handleSlashCommandConfirmationRequest(it)
                     is MessageComposerViewEvents.SendMessageResult               -> renderSendMessageResult(it)
                     is MessageComposerViewEvents.ShowMessage                     -> showSnackWithMessage(it.message)
                     is MessageComposerViewEvents.ShowRoomUpgradeDialog           -> handleShowRoomUpgradeDialog(it)
    @@ -445,7 +451,7 @@ class TimelineFragment @Inject constructor(
                         }
                         showErrorInSnackbar(it.throwable)
                     }
    -            }.exhaustive
    +            }
             }
     
             timelineViewModel.observeViewEvents {
    @@ -482,7 +488,8 @@ class TimelineFragment @Inject constructor(
                     RoomDetailViewEvents.StopChatEffects                     -> handleStopChatEffects()
                     is RoomDetailViewEvents.DisplayAndAcceptCall             -> acceptIncomingCall(it)
                     RoomDetailViewEvents.RoomReplacementStarted              -> handleRoomReplacement()
    -            }.exhaustive
    +                is RoomDetailViewEvents.ChangeLocationIndicator          -> handleChangeLocationIndicator(it)
    +            }
             }
     
             if (savedInstanceState == null) {
    @@ -491,6 +498,25 @@ class TimelineFragment @Inject constructor(
             }
         }
     
    +    private fun handleSlashCommandConfirmationRequest(action: MessageComposerViewEvents.SlashCommandConfirmationRequest) {
    +        when (action.parsedCommand) {
    +            is ParsedCommand.UnignoreUser -> promptUnignoreUser(action.parsedCommand)
    +            else                          -> TODO("Add case for ${action.parsedCommand.javaClass.simpleName}")
    +        }
    +        lockSendButton = false
    +    }
    +
    +    private fun promptUnignoreUser(command: ParsedCommand.UnignoreUser) {
    +        MaterialAlertDialogBuilder(requireActivity())
    +                .setTitle(R.string.room_participants_action_unignore_title)
    +                .setMessage(getString(R.string.settings_unignore_user, command.userId))
    +                .setPositiveButton(R.string.unignore) { _, _ ->
    +                    messageComposerViewModel.handle(MessageComposerAction.SlashCommandConfirmed(command))
    +                }
    +                .setNegativeButton(R.string.action_cancel, null)
    +                .show()
    +    }
    +
         private fun renderVoiceMessageMode(content: String) {
             ContentAttachmentData.fromJsonString(content)?.let { audioAttachmentData ->
                 views.voiceMessageRecorderView.isVisible = true
    @@ -616,6 +642,10 @@ class TimelineFragment @Inject constructor(
                     )
         }
     
    +    private fun handleChangeLocationIndicator(event: RoomDetailViewEvents.ChangeLocationIndicator) {
    +        views.locationLiveStatusIndicator.isVisible = event.isVisible
    +    }
    +
         private fun displayErrorMessage(error: RoomDetailViewEvents.Failure) {
             if (error.showInDialog) displayErrorDialog(error.throwable) else showErrorInSnackbar(error.throwable)
         }
    @@ -728,7 +758,7 @@ class TimelineFragment @Inject constructor(
         }
     
         private fun setupVoiceMessageView() {
    -        voiceMessagePlaybackTracker.track(VoiceMessagePlaybackTracker.RECORDING_ID, views.voiceMessageRecorderView)
    +        audioMessagePlaybackTracker.track(AudioMessagePlaybackTracker.RECORDING_ID, views.voiceMessageRecorderView)
             views.voiceMessageRecorderView.callback = object : VoiceMessageRecorderView.Callback {
     
                 override fun onVoiceRecordingStarted() {
    @@ -783,6 +813,18 @@ class TimelineFragment @Inject constructor(
                     updateRecordingUiState(RecordingUiState.Draft)
                 }
     
    +            override fun onVoiceWaveformTouchedUp(percentage: Float, duration: Int) {
    +                messageComposerViewModel.handle(
    +                        MessageComposerAction.VoiceWaveformTouchedUp(AudioMessagePlaybackTracker.RECORDING_ID, duration, percentage)
    +                )
    +            }
    +
    +            override fun onVoiceWaveformMoved(percentage: Float, duration: Int) {
    +                messageComposerViewModel.handle(
    +                        MessageComposerAction.VoiceWaveformTouchedUp(AudioMessagePlaybackTracker.RECORDING_ID, duration, percentage)
    +                )
    +            }
    +
                 private fun updateRecordingUiState(state: RecordingUiState) {
                     messageComposerViewModel.handle(
                             MessageComposerAction.OnVoiceRecordingUiStateChanged(state))
    @@ -790,6 +832,12 @@ class TimelineFragment @Inject constructor(
             }
         }
     
    +    private fun setupLiveLocationIndicator() {
    +        views.locationLiveStatusIndicator.stopButton.debouncedClicks {
    +            timelineViewModel.handle(RoomDetailAction.StopLiveLocationSharing)
    +        }
    +    }
    +
         private fun joinJitsiRoom(jitsiWidget: Widget, enableVideo: Boolean) {
             navigator.openRoomWidget(requireContext(), timelineArgs.roomId, jitsiWidget, mapOf(JitsiCallViewModel.ENABLE_VIDEO_OPTION to enableVideo))
         }
    @@ -874,7 +922,7 @@ class TimelineFragment @Inject constructor(
                     onContentAttachmentsReady(sharedData.attachmentData)
                 }
                 null                      -> Timber.v("No share data to process")
    -        }.exhaustive
    +        }
         }
     
         private fun handleSpaceShare() {
    @@ -887,6 +935,7 @@ class TimelineFragment @Inject constructor(
         }
     
         override fun onDestroyView() {
    +        audioMessagePlaybackTracker.makeAllPlaybacksIdle()
             lazyLoadedViews.unBind()
             timelineEventController.callback = null
             timelineEventController.removeModelBuildListener(modelBuildListener)
    @@ -981,7 +1030,11 @@ class TimelineFragment @Inject constructor(
             }
         }
     
    +    @SuppressLint("RestrictedApi")
         override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) {
    +        if (isThreadTimeLine()) {
    +            if (menu is MenuBuilder) menu.setOptionalIconsVisible(true)
    +        }
             super.onCreateOptionsMenu(menu, inflater)
             // We use a custom layout for this menu item, so we need to set a ClickListener
             menu.findItem(R.id.open_matrix_apps)?.let { menuItem ->
    @@ -1177,13 +1230,10 @@ class TimelineFragment @Inject constructor(
             }
     
             val messageContent: MessageContent? = event.getLastMessageContent()
    -        val nonFormattedBody = if (messageContent is MessageAudioContent && messageContent.voiceMessageIndicator != null) {
    -            val formattedDuration = DateUtils.formatElapsedTime(((messageContent.audioInfo?.duration ?: 0) / 1000).toLong())
    -            getString(R.string.voice_message_reply_content, formattedDuration)
    -        } else if (messageContent is MessagePollContent) {
    -            messageContent.getBestPollCreationInfo()?.question?.getBestQuestion()
    -        } else {
    -            messageContent?.body ?: ""
    +        val nonFormattedBody = when (messageContent) {
    +            is MessageAudioContent -> getAudioContentBodyText(messageContent)
    +            is MessagePollContent  -> messageContent.getBestPollCreationInfo()?.question?.getBestQuestion()
    +            else                   -> messageContent?.body.orEmpty()
             }
             var formattedBody: CharSequence? = null
             if (messageContent is MessageTextContent && messageContent.format == MessageFormat.FORMAT_MATRIX_HTML) {
    @@ -1222,6 +1272,15 @@ class TimelineFragment @Inject constructor(
             focusComposerAndShowKeyboard()
         }
     
    +    private fun getAudioContentBodyText(messageContent: MessageAudioContent): String {
    +        val formattedDuration = DateUtils.formatElapsedTime(((messageContent.audioInfo?.duration ?: 0) / 1000).toLong())
    +        return if (messageContent.voiceMessageIndicator != null) {
    +            getString(R.string.voice_message_reply_content, formattedDuration)
    +        } else {
    +            getString(R.string.audio_message_reply_content, messageContent.body, formattedDuration)
    +        }
    +    }
    +
         override fun onResume() {
             super.onResume()
             notificationDrawerManager.setCurrentRoom(timelineArgs.roomId)
    @@ -1240,13 +1299,13 @@ class TimelineFragment @Inject constructor(
                     insertUserDisplayNameInTextEditor(roomDetailPendingAction.userId)
                 is RoomDetailPendingAction.OpenRoom          ->
                     handleOpenRoom(RoomDetailViewEvents.OpenRoom(roomDetailPendingAction.roomId, roomDetailPendingAction.closeCurrentRoom))
    -        }.exhaustive
    +        }
         }
     
         override fun onPause() {
             super.onPause()
             notificationDrawerManager.setCurrentRoom(null)
    -        voiceMessagePlaybackTracker.unTrack(VoiceMessagePlaybackTracker.RECORDING_ID)
    +        audioMessagePlaybackTracker.pauseAllPlaybacks()
     
             if (withState(messageComposerViewModel) { it.isVoiceRecording } && requireActivity().isChangingConfigurations) {
                 // we're rotating, maintain any active recordings
    @@ -1374,6 +1433,7 @@ class TimelineFragment @Inject constructor(
                         }
                         return when (model) {
                             is MessageFileItem,
    +                        is MessageAudioItem,
                             is MessageVoiceItem,
                             is MessageImageVideoItem,
                             is MessageTextItem -> {
    @@ -1457,6 +1517,10 @@ class TimelineFragment @Inject constructor(
     
             views.composerLayout.views.composerEmojiButton.isVisible = vectorPreferences.showEmojiKeyboard()
     
    +        if (isThreadTimeLine() && timelineArgs.threadTimelineArgs?.startsThread == true) {
    +            // Show keyboard when the user started a thread
    +            views.composerLayout.views.composerEditText.showKeyboard(andRequestFocus = true)
    +        }
             views.composerLayout.callback = object : MessageComposerView.Callback {
                 override fun onAddAttachment() {
                     if (!::attachmentTypeSelector.isInitialized) {
    @@ -1612,11 +1676,10 @@ class TimelineFragment @Inject constructor(
                     views.includeRoomToolbar.roomToolbarContentView.isClickable = roomSummary.membership == Membership.JOIN
                     views.includeRoomToolbar.roomToolbarTitleView.text = roomSummary.displayName
                     avatarRenderer.render(roomSummary.toMatrixItem(), views.includeRoomToolbar.roomToolbarAvatarImageView)
    -                views.includeRoomToolbar.roomToolbarDecorationImageView.render(roomSummary.roomEncryptionTrustLevel)
    -                views.includeRoomToolbar.roomToolbarPresenceImageView.render(
    -                        roomSummary.isDirect && matrixConfiguration.presenceSyncEnabled,
    -                        roomSummary.directUserPresence
    -                )
    +                val showPresence = roomSummary.isDirect
    +                views.includeRoomToolbar.roomToolbarPresenceImageView.render(showPresence, roomSummary.directUserPresence)
    +                val shieldView = if (showPresence) views.includeRoomToolbar.roomToolbarTitleShield else views.includeRoomToolbar.roomToolbarAvatarShield
    +                shieldView.render(roomSummary.roomEncryptionTrustLevel)
                     views.includeRoomToolbar.roomToolbarPublicImageView.isVisible = roomSummary.isPublic && !roomSummary.isDirect
                 }
             } else {
    @@ -1644,9 +1707,7 @@ class TimelineFragment @Inject constructor(
                     displayCommandError(getString(R.string.unrecognized_command, sendMessageResult.command))
                 }
                 is MessageComposerViewEvents.SlashCommandResultOk              -> {
    -                dismissLoadingDialog()
    -                views.composerLayout.setTextIfDifferent("")
    -                sendMessageResult.messageRes?.let { showSnackWithMessage(getString(it)) }
    +                handleSlashCommandResultOk(sendMessageResult.parsedCommand)
                 }
                 is MessageComposerViewEvents.SlashCommandResultError           -> {
                     dismissLoadingDialog()
    @@ -1658,11 +1719,22 @@ class TimelineFragment @Inject constructor(
                 is MessageComposerViewEvents.SlashCommandNotSupportedInThreads -> {
                     displayCommandError(getString(R.string.command_not_supported_in_threads, sendMessageResult.command.command))
                 }
    -        } // .exhaustive
    +        }
     
             lockSendButton = false
         }
     
    +    private fun handleSlashCommandResultOk(parsedCommand: ParsedCommand) {
    +        dismissLoadingDialog()
    +        views.composerLayout.setTextIfDifferent("")
    +        when (parsedCommand) {
    +            is ParsedCommand.SetMarkdown -> {
    +                showSnackWithMessage(getString(if (parsedCommand.enable) R.string.markdown_has_been_enabled else R.string.markdown_has_been_disabled))
    +            }
    +            else                         -> Unit
    +        }
    +    }
    +
         private fun displayCommandError(message: String) {
             MaterialAlertDialogBuilder(requireActivity())
                     .setTitle(R.string.command_error)
    @@ -1782,6 +1854,7 @@ class TimelineFragment @Inject constructor(
                             transactionId = data.transactionId,
                     ).show(parentFragmentManager, "REQ")
                 }
    +            else                                          -> Unit
             }
         }
     
    @@ -1871,12 +1944,16 @@ class TimelineFragment @Inject constructor(
             vectorBaseActivity.notImplemented("encrypted message click")
         }
     
    -    override fun onImageMessageClicked(messageImageContent: MessageImageInfoContent, mediaData: ImageContentRenderer.Data, view: View) {
    +    override fun onImageMessageClicked(messageImageContent: MessageImageInfoContent,
    +                                       mediaData: ImageContentRenderer.Data,
    +                                       view: View,
    +                                       inMemory: List) {
             navigator.openMediaViewer(
                     activity = requireActivity(),
                     roomId = timelineArgs.roomId,
                     mediaData = mediaData,
    -                view = view
    +                view = view,
    +                inMemory = inMemory
             ) { pairs ->
                 pairs.add(Pair(views.roomToolbar, ViewCompat.getTransitionName(views.roomToolbar) ?: ""))
                 pairs.add(Pair(views.composerLayout, ViewCompat.getTransitionName(views.composerLayout) ?: ""))
    @@ -2047,6 +2124,18 @@ class TimelineFragment @Inject constructor(
             messageComposerViewModel.handle(MessageComposerAction.PlayOrPauseVoicePlayback(eventId, messageAudioContent))
         }
     
    +    override fun onVoiceWaveformTouchedUp(eventId: String, duration: Int, percentage: Float) {
    +        messageComposerViewModel.handle(MessageComposerAction.VoiceWaveformTouchedUp(eventId, duration, percentage))
    +    }
    +
    +    override fun onVoiceWaveformMovedTo(eventId: String, duration: Int, percentage: Float) {
    +        messageComposerViewModel.handle(MessageComposerAction.VoiceWaveformMovedTo(eventId, duration, percentage))
    +    }
    +
    +    override fun onAudioSeekBarMovedTo(eventId: String, duration: Int, percentage: Float) {
    +        messageComposerViewModel.handle(MessageComposerAction.AudioSeekBarMovedTo(eventId, duration, percentage))
    +    }
    +
         private fun onShareActionClicked(action: EventSharedAction.Share) {
             when (action.messageContent) {
                 is MessageTextContent           -> shareText(requireContext(), action.messageContent.body)
    @@ -2175,7 +2264,7 @@ class TimelineFragment @Inject constructor(
                 }
                 is EventSharedAction.ReplyInThread              -> {
                     if (withState(messageComposerViewModel) { it.isVoiceMessageIdle }) {
    -                    navigateToThreadTimeline(action.eventId, action.startsThread)
    +                    onReplyInThreadClicked(action)
                     } else {
                         requireActivity().toast(R.string.error_voice_message_cannot_reply_or_edit)
                     }
    @@ -2232,6 +2321,8 @@ class TimelineFragment @Inject constructor(
                 is EventSharedAction.EndPoll                    -> {
                     askConfirmationToEndPoll(action.eventId)
                 }
    +            is EventSharedAction.ReportContent              -> Unit /* Not clickable */
    +            EventSharedAction.Separator                     -> Unit /* Not clickable */
             }
         }
     
    @@ -2329,6 +2420,14 @@ class TimelineFragment @Inject constructor(
                     .show()
         }
     
    +    private fun onReplyInThreadClicked(action: EventSharedAction.ReplyInThread) {
    +        if (vectorPreferences.areThreadMessagesEnabled()) {
    +            navigateToThreadTimeline(action.eventId, action.startsThread)
    +        } else {
    +            displayThreadsBetaOptInDialog()
    +        }
    +    }
    +
         /**
          * Navigate to Threads timeline for the specified rootThreadEventId
          * using the ThreadsActivity
    @@ -2348,6 +2447,25 @@ class TimelineFragment @Inject constructor(
             }
         }
     
    +    private fun displayThreadsBetaOptInDialog() {
    +        activity?.let {
    +            MaterialAlertDialogBuilder(it)
    +                    .setTitle(R.string.threads_beta_enable_notice_title)
    +                    .setMessage(threadsManager.getBetaEnableThreadsMessage())
    +                    .setCancelable(true)
    +                    .setNegativeButton(R.string.action_not_now) { _, _ -> }
    +                    .setPositiveButton(R.string.action_try_it_out) { _, _ ->
    +                        threadsManager.enableThreadsAndRestart(it)
    +                    }
    +                    .show()
    +                    ?.findViewById(android.R.id.message)
    +                    ?.apply {
    +                        linksClickable = true
    +                        movementMethod = LinkMovementMethod.getInstance()
    +                    }
    +        }
    +    }
    +
         /**
          * Navigate to Threads list for the current room
          * using the ThreadsActivity
    @@ -2430,7 +2548,7 @@ class TimelineFragment @Inject constructor(
                                     locationOwnerId = session.myUserId
                             )
                 }
    -        }.exhaustive
    +        }
         }
     
         // AttachmentsHelper.Callback
    diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/TimelineViewModel.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/TimelineViewModel.kt
    index a9235b5699..91a4007ca6 100644
    --- a/vector/src/main/java/im/vector/app/features/home/room/detail/TimelineViewModel.kt
    +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/TimelineViewModel.kt
    @@ -33,7 +33,6 @@ import im.vector.app.BuildConfig
     import im.vector.app.R
     import im.vector.app.core.di.MavericksAssistedViewModelFactory
     import im.vector.app.core.di.hiltMavericksViewModelFactory
    -import im.vector.app.core.extensions.exhaustive
     import im.vector.app.core.mvrx.runCatchingToAsync
     import im.vector.app.core.platform.VectorViewModel
     import im.vector.app.core.resources.StringProvider
    @@ -53,6 +52,7 @@ import im.vector.app.features.home.room.detail.sticker.StickerPickerActionHandle
     import im.vector.app.features.home.room.detail.timeline.factory.TimelineFactory
     import im.vector.app.features.home.room.detail.timeline.url.PreviewUrlRetriever
     import im.vector.app.features.home.room.typing.TypingHelper
    +import im.vector.app.features.location.LocationSharingServiceConnection
     import im.vector.app.features.notifications.NotificationDrawerManager
     import im.vector.app.features.powerlevel.PowerLevelsFlowFactory
     import im.vector.app.features.session.coroutineScope
    @@ -80,11 +80,13 @@ import org.matrix.android.sdk.api.session.crypto.MXCryptoError
     import org.matrix.android.sdk.api.session.events.model.EventType
     import org.matrix.android.sdk.api.session.events.model.LocalEcho
     import org.matrix.android.sdk.api.session.events.model.RelationType
    +import org.matrix.android.sdk.api.session.events.model.content.WithHeldCode
     import org.matrix.android.sdk.api.session.events.model.isAttachmentMessage
     import org.matrix.android.sdk.api.session.events.model.isTextMessage
     import org.matrix.android.sdk.api.session.events.model.toContent
     import org.matrix.android.sdk.api.session.events.model.toModel
     import org.matrix.android.sdk.api.session.file.FileService
    +import org.matrix.android.sdk.api.session.getRoom
     import org.matrix.android.sdk.api.session.initsync.SyncStatusService
     import org.matrix.android.sdk.api.session.room.members.ChangeMembershipState
     import org.matrix.android.sdk.api.session.room.members.roomMemberQueryParams
    @@ -104,7 +106,6 @@ import org.matrix.android.sdk.api.session.widgets.model.WidgetType
     import org.matrix.android.sdk.api.util.toOptional
     import org.matrix.android.sdk.flow.flow
     import org.matrix.android.sdk.flow.unwrap
    -import org.matrix.android.sdk.internal.crypto.model.event.WithHeldCode
     import timber.log.Timber
     import java.util.concurrent.atomic.AtomicBoolean
     
    @@ -125,10 +126,11 @@ class TimelineViewModel @AssistedInject constructor(
             private val activeConferenceHolder: JitsiActiveConferenceHolder,
             private val decryptionFailureTracker: DecryptionFailureTracker,
             private val notificationDrawerManager: NotificationDrawerManager,
    +        private val locationSharingServiceConnection: LocationSharingServiceConnection,
             timelineFactory: TimelineFactory,
             appStateHandler: AppStateHandler
     ) : VectorViewModel(initialState),
    -        Timeline.Listener, ChatEffectManager.Delegate, CallProtocolsChecker.Listener {
    +        Timeline.Listener, ChatEffectManager.Delegate, CallProtocolsChecker.Listener, LocationSharingServiceConnection.Callback {
     
         private val room = session.getRoom(initialState.roomId)!!
         private val eventId = initialState.eventId
    @@ -184,7 +186,7 @@ class TimelineViewModel @AssistedInject constructor(
             }
             // Inform the SDK that the room is displayed
             viewModelScope.launch(Dispatchers.IO) {
    -            tryOrNull { session.onRoomDisplayed(initialState.roomId) }
    +            tryOrNull { session.roomService().onRoomDisplayed(initialState.roomId) }
             }
             callManager.addProtocolsCheckerListener(this)
             callManager.checkForProtocolsSupportIfNeeded()
    @@ -220,6 +222,9 @@ class TimelineViewModel @AssistedInject constructor(
     
             // Threads
             initThreads()
    +
    +        // Observe location service lifecycle to be able to warn the user
    +        locationSharingServiceConnection.bind(this)
         }
     
         /**
    @@ -440,7 +445,8 @@ class TimelineViewModel @AssistedInject constructor(
                     _viewEvents.post(RoomDetailViewEvents.OpenRoom(action.replacementRoomId, closeCurrentRoom = true))
                 }
                 is RoomDetailAction.EndPoll                          -> handleEndPoll(action.eventId)
    -        }.exhaustive
    +            RoomDetailAction.StopLiveLocationSharing             -> handleStopLiveLocationSharing()
    +        }
         }
     
         private fun handleJitsiCallJoinStatus(action: RoomDetailAction.UpdateJoinJitsiCallStatus) = withState { state ->
    @@ -677,7 +683,7 @@ class TimelineViewModel @AssistedInject constructor(
                 }
                 viewModelScope.launch {
                     val result = runCatchingToAsync {
    -                    session.joinRoom(roomId, viaServers = viaServers)
    +                    session.roomService().joinRoom(roomId, viaServers = viaServers)
                         roomId
                     }
                     setState {
    @@ -706,18 +712,20 @@ class TimelineViewModel @AssistedInject constructor(
     
             if (initialState.isThreadTimeline()) {
                 when (itemId) {
    -                R.id.menu_thread_timeline_more -> true
    -                else                           -> false
    +                R.id.menu_thread_timeline_view_in_room,
    +                R.id.menu_thread_timeline_copy_link,
    +                R.id.menu_thread_timeline_share -> true
    +                else                            -> false
                 }
             } else {
                 when (itemId) {
                     R.id.timeline_setting          -> true
                     R.id.invite                    -> state.canInvite
                     R.id.open_matrix_apps          -> true
    -                R.id.voice_call                -> state.isWebRTCCallOptionAvailable()
    -                R.id.video_call                -> state.isWebRTCCallOptionAvailable() || state.jitsiState.confId == null || state.jitsiState.hasJoined
    +                R.id.voice_call                -> state.isCallOptionAvailable()
    +                R.id.video_call                -> state.isCallOptionAvailable() || state.jitsiState.confId == null || state.jitsiState.hasJoined
                     // Show Join conference button only if there is an active conf id not joined. Otherwise fallback to default video disabled. ^
    -                R.id.join_conference           -> !state.isWebRTCCallOptionAvailable() && state.jitsiState.confId != null && !state.jitsiState.hasJoined
    +                R.id.join_conference           -> !state.isCallOptionAvailable() && state.jitsiState.confId != null && !state.jitsiState.hasJoined
                     R.id.search                    -> state.isSearchAvailable()
                     R.id.menu_timeline_thread_list -> vectorPreferences.areThreadMessagesEnabled()
                     R.id.dev_tools                 -> vectorPreferences.developerMode()
    @@ -801,7 +809,7 @@ class TimelineViewModel @AssistedInject constructor(
             notificationDrawerManager.updateEvents { it.clearMemberShipNotificationForRoom(initialState.roomId) }
             viewModelScope.launch {
                 try {
    -                session.leaveRoom(room.roomId)
    +                session.roomService().leaveRoom(room.roomId)
                 } catch (throwable: Throwable) {
                     _viewEvents.post(RoomDetailViewEvents.Failure(throwable, showInDialog = true))
                 }
    @@ -812,7 +820,7 @@ class TimelineViewModel @AssistedInject constructor(
             notificationDrawerManager.updateEvents { it.clearMemberShipNotificationForRoom(initialState.roomId) }
             viewModelScope.launch {
                 try {
    -                session.joinRoom(room.roomId)
    +                session.roomService().joinRoom(room.roomId)
                     analyticsTracker.capture(room.roomSummary().toAnalyticsJoinedRoom())
                 } catch (throwable: Throwable) {
                     _viewEvents.post(RoomDetailViewEvents.Failure(throwable, showInDialog = true))
    @@ -989,7 +997,7 @@ class TimelineViewModel @AssistedInject constructor(
     
             viewModelScope.launch {
                 val event = try {
    -                session.ignoreUserIds(listOf(action.userId))
    +                session.userService().ignoreUserIds(listOf(action.userId))
                     RoomDetailViewEvents.ActionSuccess(action)
                 } catch (failure: Throwable) {
                     RoomDetailViewEvents.ActionFailure(action, failure)
    @@ -1079,7 +1087,7 @@ class TimelineViewModel @AssistedInject constructor(
                         copy(syncState = syncState)
                     }
     
    -        session.getSyncStatusLive()
    +        session.syncStatusService().getSyncStatusLive()
                     .asFlow()
                     .filterIsInstance()
                     .setOnEach {
    @@ -1087,6 +1095,10 @@ class TimelineViewModel @AssistedInject constructor(
                     }
         }
     
    +    private fun handleStopLiveLocationSharing() {
    +        locationSharingServiceConnection.stopLiveLocationSharing(room.roomId)
    +    }
    +
         private fun observeRoomSummary() {
             room.flow().liveRoomSummary()
                     .unwrap()
    @@ -1105,9 +1117,13 @@ class TimelineViewModel @AssistedInject constructor(
                 computeUnreadState(timelineEvents, roomSummary)
             }
                     // We don't want live update of unread so we skip when we already had a HasUnread or HasNoUnread
    +                // However, we want to update an existing HasUnread, if the readMarkerId hasn't changed,
    +                // as we might be loading new events to fill gaps in the timeline.
                     .distinctUntilChanged { previous, current ->
                         when {
                             previous is UnreadState.Unknown || previous is UnreadState.ReadMarkerNotLoaded -> false
    +                        previous is UnreadState.HasUnread && current is UnreadState.HasUnread &&
    +                                previous.readMarkerId == current.readMarkerId                          -> false
                             current is UnreadState.HasUnread || current is UnreadState.HasNoUnread         -> true
                             else                                                                           -> false
                         }
    @@ -1126,12 +1142,17 @@ class TimelineViewModel @AssistedInject constructor(
                     } else {
                         UnreadState.Unknown
                     }
    +        // If the read marker is at the bottom-most event, this doesn't mean we read all, in case we just haven't loaded more events.
    +        // Avoid incorrectly returning HasNoUnread in this case.
    +        if (firstDisplayableEventIndex == 0 && timeline.hasMoreToLoad(Timeline.Direction.FORWARDS)) {
    +            return UnreadState.Unknown
    +        }
             for (i in (firstDisplayableEventIndex - 1) downTo 0) {
                 val timelineEvent = events.getOrNull(i) ?: return UnreadState.Unknown
                 val eventId = timelineEvent.root.eventId ?: return UnreadState.Unknown
                 val isFromMe = timelineEvent.root.senderId == session.myUserId
                 if (!isFromMe) {
    -                return UnreadState.HasUnread(eventId)
    +                return UnreadState.HasUnread(eventId, readMarkerIdSnapshot)
                 }
             }
             return UnreadState.HasNoUnread
    @@ -1170,7 +1191,7 @@ class TimelineViewModel @AssistedInject constructor(
                 }
                 if (summary.membership == Membership.INVITE) {
                     summary.inviterId?.let { inviterId ->
    -                    session.getRoomMember(inviterId, summary.roomId)
    +                    session.roomService().getRoomMember(inviterId, summary.roomId)
                     }?.also {
                         setState { copy(asyncInviter = Success(it)) }
                     }
    @@ -1219,6 +1240,16 @@ class TimelineViewModel @AssistedInject constructor(
             _viewEvents.post(RoomDetailViewEvents.OnNewTimelineEvents(eventIds))
         }
     
    +    override fun onLocationServiceRunning() {
    +        _viewEvents.post(RoomDetailViewEvents.ChangeLocationIndicator(isVisible = true))
    +    }
    +
    +    override fun onLocationServiceStopped() {
    +        _viewEvents.post(RoomDetailViewEvents.ChangeLocationIndicator(isVisible = false))
    +        // Bind again in case user decides to share live location without leaving the room
    +        locationSharingServiceConnection.bind(this)
    +    }
    +
         override fun onCleared() {
             timeline.dispose()
             timeline.removeAllListeners()
    @@ -1232,6 +1263,7 @@ class TimelineViewModel @AssistedInject constructor(
             // we should also mark it as read here, for the scenario that the user
             // is already in the thread timeline
             markThreadTimelineAsReadLocal()
    +        locationSharingServiceConnection.unbind()
             super.onCleared()
         }
     }
    diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/composer/VoiceMessageHelper.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/composer/AudioMessageHelper.kt
    similarity index 83%
    rename from vector/src/main/java/im/vector/app/features/home/room/detail/composer/VoiceMessageHelper.kt
    rename to vector/src/main/java/im/vector/app/features/home/room/detail/composer/AudioMessageHelper.kt
    index 735d356476..f4cab3305d 100644
    --- a/vector/src/main/java/im/vector/app/features/home/room/detail/composer/VoiceMessageHelper.kt
    +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/composer/AudioMessageHelper.kt
    @@ -21,7 +21,7 @@ import android.media.AudioAttributes
     import android.media.MediaPlayer
     import androidx.core.content.FileProvider
     import im.vector.app.BuildConfig
    -import im.vector.app.features.home.room.detail.timeline.helper.VoiceMessagePlaybackTracker
    +import im.vector.app.features.home.room.detail.timeline.helper.AudioMessagePlaybackTracker
     import im.vector.app.features.voice.VoiceFailure
     import im.vector.app.features.voice.VoiceRecorder
     import im.vector.app.features.voice.VoiceRecorderProvider
    @@ -40,12 +40,13 @@ import javax.inject.Inject
     /**
      * Helper class to record audio for voice messages.
      */
    -class VoiceMessageHelper @Inject constructor(
    +class AudioMessageHelper @Inject constructor(
             private val context: Context,
    -        private val playbackTracker: VoiceMessagePlaybackTracker,
    +        private val playbackTracker: AudioMessagePlaybackTracker,
             voiceRecorderProvider: VoiceRecorderProvider
     ) {
         private var mediaPlayer: MediaPlayer? = null
    +    private var currentPlayingId: String? = null
         private var voiceRecorder: VoiceRecorder = voiceRecorderProvider.provideVoiceRecorder()
     
         private val amplitudeList = mutableListOf()
    @@ -58,7 +59,7 @@ class VoiceMessageHelper @Inject constructor(
             amplitudeList.clear()
             attachmentData.waveform?.let {
                 amplitudeList.addAll(it)
    -            playbackTracker.updateCurrentRecording(VoiceMessagePlaybackTracker.RECORDING_ID, amplitudeList)
    +            playbackTracker.updateCurrentRecording(AudioMessagePlaybackTracker.RECORDING_ID, amplitudeList)
             }
         }
     
    @@ -127,14 +128,17 @@ class VoiceMessageHelper @Inject constructor(
     
         fun startOrPauseRecordingPlayback() {
             voiceRecorder.getCurrentRecord()?.let {
    -            startOrPausePlayback(VoiceMessagePlaybackTracker.RECORDING_ID, it)
    +            startOrPausePlayback(AudioMessagePlaybackTracker.RECORDING_ID, it)
             }
         }
     
         fun startOrPausePlayback(id: String, file: File) {
    -        stopPlayback()
    +        val playbackState = playbackTracker.getPlaybackState(id)
    +        mediaPlayer?.stop()
    +        stopPlaybackTicker()
             stopRecordingAmplitudes()
    -        if (playbackTracker.getPlaybackState(id) is VoiceMessagePlaybackTracker.Listener.State.Playing) {
    +        currentPlayingId = null
    +        if (playbackState is AudioMessagePlaybackTracker.Listener.State.Playing) {
                 playbackTracker.pausePlayback(id)
             } else {
                 startPlayback(id, file)
    @@ -161,6 +165,7 @@ class VoiceMessageHelper @Inject constructor(
                         seekTo(currentPlaybackTime)
                     }
                 }
    +            currentPlayingId = id
             } catch (failure: Throwable) {
                 Timber.e(failure, "Unable to start playback")
                 throw VoiceFailure.UnableToPlay(failure)
    @@ -169,9 +174,24 @@ class VoiceMessageHelper @Inject constructor(
         }
     
         fun stopPlayback() {
    -        playbackTracker.stopPlayback(VoiceMessagePlaybackTracker.RECORDING_ID)
    +        playbackTracker.pausePlayback(AudioMessagePlaybackTracker.RECORDING_ID)
             mediaPlayer?.stop()
             stopPlaybackTicker()
    +        currentPlayingId = null
    +    }
    +
    +    fun movePlaybackTo(id: String, percentage: Float, totalDuration: Int) {
    +        val toMillisecond = (totalDuration * percentage).toInt()
    +        playbackTracker.pauseAllPlaybacks()
    +
    +        if (currentPlayingId == id) {
    +            mediaPlayer?.seekTo(toMillisecond)
    +            playbackTracker.updatePlayingAtPlaybackTime(id, toMillisecond, percentage)
    +        } else {
    +            mediaPlayer?.pause()
    +            playbackTracker.updatePausedAtPlaybackTime(id, toMillisecond, percentage)
    +            stopPlaybackTicker()
    +        }
         }
     
         private fun startRecordingAmplitudes() {
    @@ -190,7 +210,7 @@ class VoiceMessageHelper @Inject constructor(
             try {
                 val maxAmplitude = voiceRecorder.getMaxAmplitude()
                 amplitudeList.add(maxAmplitude)
    -            playbackTracker.updateCurrentRecording(VoiceMessagePlaybackTracker.RECORDING_ID, amplitudeList)
    +            playbackTracker.updateCurrentRecording(AudioMessagePlaybackTracker.RECORDING_ID, amplitudeList)
             } catch (e: IllegalStateException) {
                 Timber.e(e, "Cannot get max amplitude. Amplitude recording timer will be stopped.")
                 stopRecordingAmplitudes()
    @@ -221,7 +241,9 @@ class VoiceMessageHelper @Inject constructor(
         private fun onPlaybackTick(id: String) {
             if (mediaPlayer?.isPlaying.orFalse()) {
                 val currentPosition = mediaPlayer?.currentPosition ?: 0
    -            playbackTracker.updateCurrentPlaybackTime(id, currentPosition)
    +            val totalDuration = mediaPlayer?.duration ?: 0
    +            val percentage = currentPosition.toFloat() / totalDuration
    +            playbackTracker.updatePlayingAtPlaybackTime(id, currentPosition, percentage)
             } else {
                 playbackTracker.stopPlayback(id)
                 stopPlaybackTicker()
    diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/composer/MessageComposerAction.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/composer/MessageComposerAction.kt
    index 10cef39942..0da324ffc2 100644
    --- a/vector/src/main/java/im/vector/app/features/home/room/detail/composer/MessageComposerAction.kt
    +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/composer/MessageComposerAction.kt
    @@ -17,6 +17,7 @@
     package im.vector.app.features.home.room.detail.composer
     
     import im.vector.app.core.platform.VectorViewModelAction
    +import im.vector.app.features.command.ParsedCommand
     import im.vector.app.features.home.room.detail.composer.voice.VoiceMessageRecorderView
     import org.matrix.android.sdk.api.session.content.ContentAttachmentData
     import org.matrix.android.sdk.api.session.room.model.message.MessageAudioContent
    @@ -30,6 +31,7 @@ sealed class MessageComposerAction : VectorViewModelAction {
         data class UserIsTyping(val isTyping: Boolean) : MessageComposerAction()
         data class OnTextChanged(val text: CharSequence) : MessageComposerAction()
         data class OnEntersBackground(val composerText: String) : MessageComposerAction()
    +    data class SlashCommandConfirmed(val parsedCommand: ParsedCommand) : MessageComposerAction()
     
         // Voice Message
         data class InitializeVoiceRecorder(val attachmentData: ContentAttachmentData) : MessageComposerAction()
    @@ -40,4 +42,7 @@ sealed class MessageComposerAction : VectorViewModelAction {
         data class PlayOrPauseVoicePlayback(val eventId: String, val messageAudioContent: MessageAudioContent) : MessageComposerAction()
         object PlayOrPauseRecordingPlayback : MessageComposerAction()
         data class EndAllVoiceActions(val deleteRecord: Boolean = true) : MessageComposerAction()
    +    data class VoiceWaveformTouchedUp(val eventId: String, val duration: Int, val percentage: Float) : MessageComposerAction()
    +    data class VoiceWaveformMovedTo(val eventId: String, val duration: Int, val percentage: Float) : MessageComposerAction()
    +    data class AudioSeekBarMovedTo(val eventId: String, val duration: Int, val percentage: Float) : MessageComposerAction()
     }
    diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/composer/MessageComposerViewEvents.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/composer/MessageComposerViewEvents.kt
    index c1af838795..e1f6923d21 100644
    --- a/vector/src/main/java/im/vector/app/features/home/room/detail/composer/MessageComposerViewEvents.kt
    +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/composer/MessageComposerViewEvents.kt
    @@ -16,9 +16,9 @@
     
     package im.vector.app.features.home.room.detail.composer
     
    -import androidx.annotation.StringRes
     import im.vector.app.core.platform.VectorViewEvents
     import im.vector.app.features.command.Command
    +import im.vector.app.features.command.ParsedCommand
     
     sealed class MessageComposerViewEvents : VectorViewEvents {
     
    @@ -30,13 +30,14 @@ sealed class MessageComposerViewEvents : VectorViewEvents {
     
         object MessageSent : SendMessageResult()
         data class JoinRoomCommandSuccess(val roomId: String) : SendMessageResult()
    -    class SlashCommandError(val command: Command) : SendMessageResult()
    -    class SlashCommandUnknown(val command: String) : SendMessageResult()
    -    class SlashCommandNotSupportedInThreads(val command: Command) : SendMessageResult()
    -    data class SlashCommandHandled(@StringRes val messageRes: Int? = null) : SendMessageResult()
    +    data class SlashCommandError(val command: Command) : SendMessageResult()
    +    data class SlashCommandUnknown(val command: String) : SendMessageResult()
    +    data class SlashCommandNotSupportedInThreads(val command: Command) : SendMessageResult()
         object SlashCommandLoading : SendMessageResult()
    -    data class SlashCommandResultOk(@StringRes val messageRes: Int? = null) : SendMessageResult()
    -    class SlashCommandResultError(val throwable: Throwable) : SendMessageResult()
    +    data class SlashCommandResultOk(val parsedCommand: ParsedCommand) : SendMessageResult()
    +    data class SlashCommandResultError(val throwable: Throwable) : SendMessageResult()
    +
    +    data class SlashCommandConfirmationRequest(val parsedCommand: ParsedCommand) : MessageComposerViewEvents()
     
         data class OpenRoomMemberProfile(val userId: String) : MessageComposerViewEvents()
     
    diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/composer/MessageComposerViewModel.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/composer/MessageComposerViewModel.kt
    index 009d898940..a67333e5d5 100644
    --- a/vector/src/main/java/im/vector/app/features/home/room/detail/composer/MessageComposerViewModel.kt
    +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/composer/MessageComposerViewModel.kt
    @@ -23,7 +23,6 @@ import dagger.assisted.AssistedInject
     import im.vector.app.R
     import im.vector.app.core.di.MavericksAssistedViewModelFactory
     import im.vector.app.core.di.hiltMavericksViewModelFactory
    -import im.vector.app.core.extensions.exhaustive
     import im.vector.app.core.platform.VectorViewModel
     import im.vector.app.core.resources.StringProvider
     import im.vector.app.features.analytics.AnalyticsTracker
    @@ -51,6 +50,8 @@ import org.matrix.android.sdk.api.session.events.model.getRootThreadEventId
     import org.matrix.android.sdk.api.session.events.model.isThread
     import org.matrix.android.sdk.api.session.events.model.toContent
     import org.matrix.android.sdk.api.session.events.model.toModel
    +import org.matrix.android.sdk.api.session.getRoom
    +import org.matrix.android.sdk.api.session.getRoomSummary
     import org.matrix.android.sdk.api.session.room.model.PowerLevelsContent
     import org.matrix.android.sdk.api.session.room.model.RoomAvatarContent
     import org.matrix.android.sdk.api.session.room.model.RoomEncryptionAlgorithm
    @@ -74,7 +75,7 @@ class MessageComposerViewModel @AssistedInject constructor(
             private val vectorPreferences: VectorPreferences,
             private val commandParser: CommandParser,
             private val rainbowGenerator: RainbowGenerator,
    -        private val voiceMessageHelper: VoiceMessageHelper,
    +        private val audioMessageHelper: AudioMessageHelper,
             private val analyticsTracker: AnalyticsTracker,
             private val voicePlayerHelper: VoicePlayerHelper
     ) : VectorViewModel(initialState) {
    @@ -91,7 +92,6 @@ class MessageComposerViewModel @AssistedInject constructor(
         }
     
         override fun handle(action: MessageComposerAction) {
    -        Timber.v("Handle action: $action")
             when (action) {
                 is MessageComposerAction.EnterEditMode                  -> handleEnterEditMode(action)
                 is MessageComposerAction.EnterQuoteMode                 -> handleEnterQuoteMode(action)
    @@ -109,6 +109,10 @@ class MessageComposerViewModel @AssistedInject constructor(
                 is MessageComposerAction.EndAllVoiceActions             -> handleEndAllVoiceActions(action.deleteRecord)
                 is MessageComposerAction.InitializeVoiceRecorder        -> handleInitializeVoiceRecorder(action.attachmentData)
                 is MessageComposerAction.OnEntersBackground             -> handleEntersBackground(action.composerText)
    +            is MessageComposerAction.VoiceWaveformTouchedUp         -> handleVoiceWaveformTouchedUp(action)
    +            is MessageComposerAction.VoiceWaveformMovedTo           -> handleVoiceWaveformMovedTo(action)
    +            is MessageComposerAction.AudioSeekBarMovedTo            -> handleAudioSeekBarMovedTo(action)
    +            is MessageComposerAction.SlashCommandConfirmed          -> handleSlashCommandConfirmed(action)
             }
         }
     
    @@ -194,7 +198,7 @@ class MessageComposerViewModel @AssistedInject constructor(
                 }
                 when (state.sendMode) {
                     is SendMode.Regular -> {
    -                    when (val slashCommandResult = commandParser.parseSlashCommand(
    +                    when (val parsedCommand = commandParser.parseSlashCommand(
                                 textMessage = action.text,
                                 isInThreadTimeline = state.isInThreadTimeline())) {
                             is ParsedCommand.ErrorNotACommand                  -> {
    @@ -212,118 +216,117 @@ class MessageComposerViewModel @AssistedInject constructor(
                                 popDraft()
                             }
                             is ParsedCommand.ErrorSyntax                       -> {
    -                            _viewEvents.post(MessageComposerViewEvents.SlashCommandError(slashCommandResult.command))
    +                            _viewEvents.post(MessageComposerViewEvents.SlashCommandError(parsedCommand.command))
                             }
                             is ParsedCommand.ErrorEmptySlashCommand            -> {
                                 _viewEvents.post(MessageComposerViewEvents.SlashCommandUnknown("/"))
                             }
                             is ParsedCommand.ErrorUnknownSlashCommand          -> {
    -                            _viewEvents.post(MessageComposerViewEvents.SlashCommandUnknown(slashCommandResult.slashCommand))
    +                            _viewEvents.post(MessageComposerViewEvents.SlashCommandUnknown(parsedCommand.slashCommand))
                             }
                             is ParsedCommand.ErrorCommandNotSupportedInThreads -> {
    -                            _viewEvents.post(MessageComposerViewEvents.SlashCommandNotSupportedInThreads(slashCommandResult.command))
    +                            _viewEvents.post(MessageComposerViewEvents.SlashCommandNotSupportedInThreads(parsedCommand.command))
                             }
                             is ParsedCommand.SendPlainText                     -> {
                                 // Send the text message to the room, without markdown
                                 if (state.rootThreadEventId != null) {
                                     room.replyInThread(
                                             rootThreadEventId = state.rootThreadEventId,
    -                                        replyInThreadText = slashCommandResult.message,
    +                                        replyInThreadText = parsedCommand.message,
                                             autoMarkdown = false)
                                 } else {
    -                                room.sendTextMessage(slashCommandResult.message, autoMarkdown = false)
    +                                room.sendTextMessage(parsedCommand.message, autoMarkdown = false)
                                 }
                                 _viewEvents.post(MessageComposerViewEvents.MessageSent)
                                 popDraft()
                             }
                             is ParsedCommand.ChangeRoomName                    -> {
    -                            handleChangeRoomNameSlashCommand(slashCommandResult)
    +                            handleChangeRoomNameSlashCommand(parsedCommand)
                             }
                             is ParsedCommand.Invite                            -> {
    -                            handleInviteSlashCommand(slashCommandResult)
    +                            handleInviteSlashCommand(parsedCommand)
                             }
                             is ParsedCommand.Invite3Pid                        -> {
    -                            handleInvite3pidSlashCommand(slashCommandResult)
    +                            handleInvite3pidSlashCommand(parsedCommand)
                             }
                             is ParsedCommand.SetUserPowerLevel                 -> {
    -                            handleSetUserPowerLevel(slashCommandResult)
    +                            handleSetUserPowerLevel(parsedCommand)
                             }
                             is ParsedCommand.ClearScalarToken                  -> {
                                 // TODO
                                 _viewEvents.post(MessageComposerViewEvents.SlashCommandNotImplemented)
                             }
                             is ParsedCommand.SetMarkdown                       -> {
    -                            vectorPreferences.setMarkdownEnabled(slashCommandResult.enable)
    -                            _viewEvents.post(MessageComposerViewEvents.SlashCommandResultOk(
    -                                    if (slashCommandResult.enable) R.string.markdown_has_been_enabled else R.string.markdown_has_been_disabled))
    +                            vectorPreferences.setMarkdownEnabled(parsedCommand.enable)
    +                            _viewEvents.post(MessageComposerViewEvents.SlashCommandResultOk(parsedCommand))
                                 popDraft()
                             }
                             is ParsedCommand.BanUser                           -> {
    -                            handleBanSlashCommand(slashCommandResult)
    +                            handleBanSlashCommand(parsedCommand)
                             }
                             is ParsedCommand.UnbanUser                         -> {
    -                            handleUnbanSlashCommand(slashCommandResult)
    +                            handleUnbanSlashCommand(parsedCommand)
                             }
                             is ParsedCommand.IgnoreUser                        -> {
    -                            handleIgnoreSlashCommand(slashCommandResult)
    +                            handleIgnoreSlashCommand(parsedCommand)
                             }
                             is ParsedCommand.UnignoreUser                      -> {
    -                            handleUnignoreSlashCommand(slashCommandResult)
    +                            handleUnignoreSlashCommand(parsedCommand)
                             }
                             is ParsedCommand.RemoveUser                        -> {
    -                            handleRemoveSlashCommand(slashCommandResult)
    +                            handleRemoveSlashCommand(parsedCommand)
                             }
                             is ParsedCommand.JoinRoom                          -> {
    -                            handleJoinToAnotherRoomSlashCommand(slashCommandResult)
    +                            handleJoinToAnotherRoomSlashCommand(parsedCommand)
                                 popDraft()
                             }
                             is ParsedCommand.PartRoom                          -> {
    -                            handlePartSlashCommand(slashCommandResult)
    +                            handlePartSlashCommand(parsedCommand)
                             }
                             is ParsedCommand.SendEmote                         -> {
                                 if (state.rootThreadEventId != null) {
                                     room.replyInThread(
                                             rootThreadEventId = state.rootThreadEventId,
    -                                        replyInThreadText = slashCommandResult.message,
    +                                        replyInThreadText = parsedCommand.message,
                                             msgType = MessageType.MSGTYPE_EMOTE,
                                             autoMarkdown = action.autoMarkdown)
                                 } else {
    -                                room.sendTextMessage(slashCommandResult.message, msgType = MessageType.MSGTYPE_EMOTE, autoMarkdown = action.autoMarkdown)
    +                                room.sendTextMessage(parsedCommand.message, msgType = MessageType.MSGTYPE_EMOTE, autoMarkdown = action.autoMarkdown)
                                 }
    -                            _viewEvents.post(MessageComposerViewEvents.SlashCommandResultOk())
    +                            _viewEvents.post(MessageComposerViewEvents.SlashCommandResultOk(parsedCommand))
                                 popDraft()
                             }
                             is ParsedCommand.SendRainbow                       -> {
    -                            val message = slashCommandResult.message.toString()
    +                            val message = parsedCommand.message.toString()
                                 if (state.rootThreadEventId != null) {
                                     room.replyInThread(
                                             rootThreadEventId = state.rootThreadEventId,
    -                                        replyInThreadText = slashCommandResult.message,
    +                                        replyInThreadText = parsedCommand.message,
                                             formattedText = rainbowGenerator.generate(message))
                                 } else {
                                     room.sendFormattedTextMessage(message, rainbowGenerator.generate(message))
                                 }
    -                            _viewEvents.post(MessageComposerViewEvents.SlashCommandResultOk())
    +                            _viewEvents.post(MessageComposerViewEvents.SlashCommandResultOk(parsedCommand))
                                 popDraft()
                             }
                             is ParsedCommand.SendRainbowEmote                  -> {
    -                            val message = slashCommandResult.message.toString()
    +                            val message = parsedCommand.message.toString()
                                 if (state.rootThreadEventId != null) {
                                     room.replyInThread(
                                             rootThreadEventId = state.rootThreadEventId,
    -                                        replyInThreadText = slashCommandResult.message,
    +                                        replyInThreadText = parsedCommand.message,
                                             msgType = MessageType.MSGTYPE_EMOTE,
                                             formattedText = rainbowGenerator.generate(message))
                                 } else {
                                     room.sendFormattedTextMessage(message, rainbowGenerator.generate(message), MessageType.MSGTYPE_EMOTE)
                                 }
     
    -                            _viewEvents.post(MessageComposerViewEvents.SlashCommandResultOk())
    +                            _viewEvents.post(MessageComposerViewEvents.SlashCommandResultOk(parsedCommand))
                                 popDraft()
                             }
                             is ParsedCommand.SendSpoiler                       -> {
    -                            val text = "[${stringProvider.getString(R.string.spoiler)}](${slashCommandResult.message})"
    -                            val formattedText = "${slashCommandResult.message}"
    +                            val text = "[${stringProvider.getString(R.string.spoiler)}](${parsedCommand.message})"
    +                            val formattedText = "${parsedCommand.message}"
                                 if (state.rootThreadEventId != null) {
                                     room.replyInThread(
                                             rootThreadEventId = state.rootThreadEventId,
    @@ -334,51 +337,51 @@ class MessageComposerViewModel @AssistedInject constructor(
                                             text,
                                             formattedText)
                                 }
    -                            _viewEvents.post(MessageComposerViewEvents.SlashCommandResultOk())
    +                            _viewEvents.post(MessageComposerViewEvents.SlashCommandResultOk(parsedCommand))
                                 popDraft()
                             }
                             is ParsedCommand.SendShrug                         -> {
    -                            sendPrefixedMessage("¯\\_(ツ)_/¯", slashCommandResult.message, state.rootThreadEventId)
    -                            _viewEvents.post(MessageComposerViewEvents.SlashCommandResultOk())
    +                            sendPrefixedMessage("¯\\_(ツ)_/¯", parsedCommand.message, state.rootThreadEventId)
    +                            _viewEvents.post(MessageComposerViewEvents.SlashCommandResultOk(parsedCommand))
                                 popDraft()
                             }
                             is ParsedCommand.SendLenny                         -> {
    -                            sendPrefixedMessage("( ͡° ͜ʖ ͡°)", slashCommandResult.message, state.rootThreadEventId)
    -                            _viewEvents.post(MessageComposerViewEvents.SlashCommandResultOk())
    +                            sendPrefixedMessage("( ͡° ͜ʖ ͡°)", parsedCommand.message, state.rootThreadEventId)
    +                            _viewEvents.post(MessageComposerViewEvents.SlashCommandResultOk(parsedCommand))
                                 popDraft()
                             }
                             is ParsedCommand.SendChatEffect                    -> {
    -                            sendChatEffect(slashCommandResult)
    -                            _viewEvents.post(MessageComposerViewEvents.SlashCommandResultOk())
    +                            sendChatEffect(parsedCommand)
    +                            _viewEvents.post(MessageComposerViewEvents.SlashCommandResultOk(parsedCommand))
                                 popDraft()
                             }
                             is ParsedCommand.ChangeTopic                       -> {
    -                            handleChangeTopicSlashCommand(slashCommandResult)
    +                            handleChangeTopicSlashCommand(parsedCommand)
                             }
                             is ParsedCommand.ChangeDisplayName                 -> {
    -                            handleChangeDisplayNameSlashCommand(slashCommandResult)
    +                            handleChangeDisplayNameSlashCommand(parsedCommand)
                             }
                             is ParsedCommand.ChangeDisplayNameForRoom          -> {
    -                            handleChangeDisplayNameForRoomSlashCommand(slashCommandResult)
    +                            handleChangeDisplayNameForRoomSlashCommand(parsedCommand)
                             }
                             is ParsedCommand.ChangeRoomAvatar                  -> {
    -                            handleChangeRoomAvatarSlashCommand(slashCommandResult)
    +                            handleChangeRoomAvatarSlashCommand(parsedCommand)
                             }
                             is ParsedCommand.ChangeAvatarForRoom               -> {
    -                            handleChangeAvatarForRoomSlashCommand(slashCommandResult)
    +                            handleChangeAvatarForRoomSlashCommand(parsedCommand)
                             }
                             is ParsedCommand.ShowUser                          -> {
    -                            _viewEvents.post(MessageComposerViewEvents.SlashCommandResultOk())
    -                            handleWhoisSlashCommand(slashCommandResult)
    +                            _viewEvents.post(MessageComposerViewEvents.SlashCommandResultOk(parsedCommand))
    +                            handleWhoisSlashCommand(parsedCommand)
                                 popDraft()
                             }
                             is ParsedCommand.DiscardSession                    -> {
                                 if (room.isEncrypted()) {
                                     session.cryptoService().discardOutboundSession(room.roomId)
    -                                _viewEvents.post(MessageComposerViewEvents.SlashCommandResultOk())
    +                                _viewEvents.post(MessageComposerViewEvents.SlashCommandResultOk(parsedCommand))
                                     popDraft()
                                 } else {
    -                                _viewEvents.post(MessageComposerViewEvents.SlashCommandResultOk())
    +                                _viewEvents.post(MessageComposerViewEvents.SlashCommandResultOk(parsedCommand))
                                     _viewEvents.post(
                                             MessageComposerViewEvents
                                                     .ShowMessage(stringProvider.getString(R.string.command_description_discard_session_not_handled))
    @@ -390,8 +393,8 @@ class MessageComposerViewModel @AssistedInject constructor(
                                 viewModelScope.launch(Dispatchers.IO) {
                                     try {
                                         val params = CreateSpaceParams().apply {
    -                                        name = slashCommandResult.name
    -                                        invitedUserIds.addAll(slashCommandResult.invitees)
    +                                        name = parsedCommand.name
    +                                        invitedUserIds.addAll(parsedCommand.invitees)
                                         }
                                         val spaceId = session.spaceService().createSpace(params)
                                         session.spaceService().getSpace(spaceId)
    @@ -402,7 +405,7 @@ class MessageComposerViewModel @AssistedInject constructor(
                                                         true
                                                 )
                                         popDraft()
    -                                    _viewEvents.post(MessageComposerViewEvents.SlashCommandResultOk())
    +                                    _viewEvents.post(MessageComposerViewEvents.SlashCommandResultOk(parsedCommand))
                                     } catch (failure: Throwable) {
                                         _viewEvents.post(MessageComposerViewEvents.SlashCommandResultError(failure))
                                     }
    @@ -413,7 +416,7 @@ class MessageComposerViewModel @AssistedInject constructor(
                                 _viewEvents.post(MessageComposerViewEvents.SlashCommandLoading)
                                 viewModelScope.launch(Dispatchers.IO) {
                                     try {
    -                                    session.spaceService().getSpace(slashCommandResult.spaceId)
    +                                    session.spaceService().getSpace(parsedCommand.spaceId)
                                                 ?.addChildren(
                                                         room.roomId,
                                                         null,
    @@ -421,7 +424,7 @@ class MessageComposerViewModel @AssistedInject constructor(
                                                         false
                                                 )
                                         popDraft()
    -                                    _viewEvents.post(MessageComposerViewEvents.SlashCommandResultOk())
    +                                    _viewEvents.post(MessageComposerViewEvents.SlashCommandResultOk(parsedCommand))
                                     } catch (failure: Throwable) {
                                         _viewEvents.post(MessageComposerViewEvents.SlashCommandResultError(failure))
                                     }
    @@ -432,9 +435,9 @@ class MessageComposerViewModel @AssistedInject constructor(
                                 _viewEvents.post(MessageComposerViewEvents.SlashCommandLoading)
                                 viewModelScope.launch(Dispatchers.IO) {
                                     try {
    -                                    session.spaceService().joinSpace(slashCommandResult.spaceIdOrAlias)
    +                                    session.spaceService().joinSpace(parsedCommand.spaceIdOrAlias)
                                         popDraft()
    -                                    _viewEvents.post(MessageComposerViewEvents.SlashCommandResultOk())
    +                                    _viewEvents.post(MessageComposerViewEvents.SlashCommandResultOk(parsedCommand))
                                     } catch (failure: Throwable) {
                                         _viewEvents.post(MessageComposerViewEvents.SlashCommandResultError(failure))
                                     }
    @@ -444,9 +447,9 @@ class MessageComposerViewModel @AssistedInject constructor(
                             is ParsedCommand.LeaveRoom                         -> {
                                 viewModelScope.launch(Dispatchers.IO) {
                                     try {
    -                                    session.leaveRoom(slashCommandResult.roomId)
    +                                    session.roomService().leaveRoom(parsedCommand.roomId)
                                         popDraft()
    -                                    _viewEvents.post(MessageComposerViewEvents.SlashCommandResultOk())
    +                                    _viewEvents.post(MessageComposerViewEvents.SlashCommandResultOk(parsedCommand))
                                     } catch (failure: Throwable) {
                                         _viewEvents.post(MessageComposerViewEvents.SlashCommandResultError(failure))
                                     }
    @@ -456,14 +459,14 @@ class MessageComposerViewModel @AssistedInject constructor(
                             is ParsedCommand.UpgradeRoom                       -> {
                                 _viewEvents.post(
                                         MessageComposerViewEvents.ShowRoomUpgradeDialog(
    -                                            slashCommandResult.newVersion,
    +                                            parsedCommand.newVersion,
                                                 room.roomSummary()?.isPublic ?: false
                                         )
                                 )
    -                            _viewEvents.post(MessageComposerViewEvents.SlashCommandResultOk())
    +                            _viewEvents.post(MessageComposerViewEvents.SlashCommandResultOk(parsedCommand))
                                 popDraft()
                             }
    -                    }.exhaustive
    +                    }
                     }
                     is SendMode.Edit    -> {
                         // is original event a reply?
    @@ -536,7 +539,7 @@ class MessageComposerViewModel @AssistedInject constructor(
                     is SendMode.Voice   -> {
                         // do nothing
                     }
    -            }.exhaustive
    +            }
             }
         }
     
    @@ -608,7 +611,7 @@ class MessageComposerViewModel @AssistedInject constructor(
         private fun handleJoinToAnotherRoomSlashCommand(command: ParsedCommand.JoinRoom) {
             viewModelScope.launch {
                 try {
    -                session.joinRoom(command.roomAlias, command.reason, emptyList())
    +                session.roomService().joinRoom(command.roomAlias, command.reason, emptyList())
                 } catch (failure: Throwable) {
                     _viewEvents.post(MessageComposerViewEvents.SlashCommandResultError(failure))
                     return@launch
    @@ -643,19 +646,19 @@ class MessageComposerViewModel @AssistedInject constructor(
         }
     
         private fun handleChangeTopicSlashCommand(changeTopic: ParsedCommand.ChangeTopic) {
    -        launchSlashCommandFlowSuspendable {
    +        launchSlashCommandFlowSuspendable(changeTopic) {
                 room.updateTopic(changeTopic.topic)
             }
         }
     
         private fun handleInviteSlashCommand(invite: ParsedCommand.Invite) {
    -        launchSlashCommandFlowSuspendable {
    +        launchSlashCommandFlowSuspendable(invite) {
                 room.invite(invite.userId, invite.reason)
             }
         }
     
         private fun handleInvite3pidSlashCommand(invite: ParsedCommand.Invite3Pid) {
    -        launchSlashCommandFlowSuspendable {
    +        launchSlashCommandFlowSuspendable(invite) {
                 room.invite3pid(invite.threePid)
             }
         }
    @@ -668,19 +671,19 @@ class MessageComposerViewModel @AssistedInject constructor(
                     ?.toContent()
                     ?: return
     
    -        launchSlashCommandFlowSuspendable {
    +        launchSlashCommandFlowSuspendable(setUserPowerLevel) {
                 room.sendStateEvent(EventType.STATE_ROOM_POWER_LEVELS, stateKey = "", newPowerLevelsContent)
             }
         }
     
         private fun handleChangeDisplayNameSlashCommand(changeDisplayName: ParsedCommand.ChangeDisplayName) {
    -        launchSlashCommandFlowSuspendable {
    -            session.setDisplayName(session.myUserId, changeDisplayName.displayName)
    +        launchSlashCommandFlowSuspendable(changeDisplayName) {
    +            session.profileService().setDisplayName(session.myUserId, changeDisplayName.displayName)
             }
         }
     
         private fun handlePartSlashCommand(command: ParsedCommand.PartRoom) {
    -        launchSlashCommandFlowSuspendable {
    +        launchSlashCommandFlowSuspendable(command) {
                 if (command.roomAlias == null) {
                     // Leave the current room
                     room
    @@ -690,31 +693,31 @@ class MessageComposerViewModel @AssistedInject constructor(
                             ?.let { session.getRoom(it) }
                 }
                         ?.let {
    -                        session.leaveRoom(it.roomId)
    +                        session.roomService().leaveRoom(it.roomId)
                         }
             }
         }
     
         private fun handleRemoveSlashCommand(removeUser: ParsedCommand.RemoveUser) {
    -        launchSlashCommandFlowSuspendable {
    +        launchSlashCommandFlowSuspendable(removeUser) {
                 room.remove(removeUser.userId, removeUser.reason)
             }
         }
     
         private fun handleBanSlashCommand(ban: ParsedCommand.BanUser) {
    -        launchSlashCommandFlowSuspendable {
    +        launchSlashCommandFlowSuspendable(ban) {
                 room.ban(ban.userId, ban.reason)
             }
         }
     
         private fun handleUnbanSlashCommand(unban: ParsedCommand.UnbanUser) {
    -        launchSlashCommandFlowSuspendable {
    +        launchSlashCommandFlowSuspendable(unban) {
                 room.unban(unban.userId, unban.reason)
             }
         }
     
         private fun handleChangeRoomNameSlashCommand(changeRoomName: ParsedCommand.ChangeRoomName) {
    -        launchSlashCommandFlowSuspendable {
    +        launchSlashCommandFlowSuspendable(changeRoomName) {
                 room.updateName(changeRoomName.name)
             }
         }
    @@ -726,7 +729,7 @@ class MessageComposerViewModel @AssistedInject constructor(
         }
     
         private fun handleChangeDisplayNameForRoomSlashCommand(changeDisplayName: ParsedCommand.ChangeDisplayNameForRoom) {
    -        launchSlashCommandFlowSuspendable {
    +        launchSlashCommandFlowSuspendable(changeDisplayName) {
                 getMyRoomMemberContent()
                         ?.copy(displayName = changeDisplayName.displayName)
                         ?.toContent()
    @@ -737,13 +740,13 @@ class MessageComposerViewModel @AssistedInject constructor(
         }
     
         private fun handleChangeRoomAvatarSlashCommand(changeAvatar: ParsedCommand.ChangeRoomAvatar) {
    -        launchSlashCommandFlowSuspendable {
    +        launchSlashCommandFlowSuspendable(changeAvatar) {
                 room.sendStateEvent(EventType.STATE_ROOM_AVATAR, stateKey = "", RoomAvatarContent(changeAvatar.url).toContent())
             }
         }
     
         private fun handleChangeAvatarForRoomSlashCommand(changeAvatar: ParsedCommand.ChangeAvatarForRoom) {
    -        launchSlashCommandFlowSuspendable {
    +        launchSlashCommandFlowSuspendable(changeAvatar) {
                 getMyRoomMemberContent()
                         ?.copy(avatarUrl = changeAvatar.url)
                         ?.toContent()
    @@ -754,14 +757,25 @@ class MessageComposerViewModel @AssistedInject constructor(
         }
     
         private fun handleIgnoreSlashCommand(ignore: ParsedCommand.IgnoreUser) {
    -        launchSlashCommandFlowSuspendable {
    -            session.ignoreUserIds(listOf(ignore.userId))
    +        launchSlashCommandFlowSuspendable(ignore) {
    +            session.userService().ignoreUserIds(listOf(ignore.userId))
             }
         }
     
         private fun handleUnignoreSlashCommand(unignore: ParsedCommand.UnignoreUser) {
    -        launchSlashCommandFlowSuspendable {
    -            session.unIgnoreUserIds(listOf(unignore.userId))
    +        _viewEvents.post(MessageComposerViewEvents.SlashCommandConfirmationRequest(unignore))
    +    }
    +
    +    private fun handleSlashCommandConfirmed(action: MessageComposerAction.SlashCommandConfirmed) {
    +        when (action.parsedCommand) {
    +            is ParsedCommand.UnignoreUser -> handleUnignoreSlashCommandConfirmed(action.parsedCommand)
    +            else                          -> TODO("Not handled yet")
    +        }
    +    }
    +
    +    private fun handleUnignoreSlashCommandConfirmed(unignore: ParsedCommand.UnignoreUser) {
    +        launchSlashCommandFlowSuspendable(unignore) {
    +            session.userService().unIgnoreUserIds(listOf(unignore.userId))
             }
         }
     
    @@ -810,18 +824,18 @@ class MessageComposerViewModel @AssistedInject constructor(
     
         private fun handleStartRecordingVoiceMessage() {
             try {
    -            voiceMessageHelper.startRecording(room.roomId)
    +            audioMessageHelper.startRecording(room.roomId)
             } catch (failure: Throwable) {
                 _viewEvents.post(MessageComposerViewEvents.VoicePlaybackOrRecordingFailure(failure))
             }
         }
     
         private fun handleEndRecordingVoiceMessage(isCancelled: Boolean, rootThreadEventId: String? = null) {
    -        voiceMessageHelper.stopPlayback()
    +        audioMessageHelper.stopPlayback()
             if (isCancelled) {
    -            voiceMessageHelper.deleteRecording()
    +            audioMessageHelper.deleteRecording()
             } else {
    -            voiceMessageHelper.stopRecording(convertForSending = true)?.let { audioType ->
    +            audioMessageHelper.stopRecording(convertForSending = true)?.let { audioType ->
                     if (audioType.duration > 1000) {
                         room.sendMedia(
                                 attachment = audioType.toContentAttachmentData(isVoiceMessage = true),
    @@ -829,7 +843,7 @@ class MessageComposerViewModel @AssistedInject constructor(
                                 roomIds = emptySet(),
                                 rootThreadEventId = rootThreadEventId)
                     } else {
    -                    voiceMessageHelper.deleteRecording()
    +                    audioMessageHelper.deleteRecording()
                     }
                 }
             }
    @@ -844,7 +858,7 @@ class MessageComposerViewModel @AssistedInject constructor(
                     // Conversion can fail, fallback to the original file in this case and let the player fail for us
                     val convertedFile = voicePlayerHelper.convertFile(audioFile) ?: audioFile
                     // Play can fail
    -                voiceMessageHelper.startOrPausePlayback(action.eventId, convertedFile)
    +                audioMessageHelper.startOrPausePlayback(action.eventId, convertedFile)
                 } catch (failure: Throwable) {
                     _viewEvents.post(MessageComposerViewEvents.VoicePlaybackOrRecordingFailure(failure))
                 }
    @@ -852,29 +866,43 @@ class MessageComposerViewModel @AssistedInject constructor(
         }
     
         private fun handlePlayOrPauseRecordingPlayback() {
    -        voiceMessageHelper.startOrPauseRecordingPlayback()
    +        audioMessageHelper.startOrPauseRecordingPlayback()
         }
     
         private fun handleEndAllVoiceActions(deleteRecord: Boolean) {
    -        voiceMessageHelper.clearTracker()
    -        voiceMessageHelper.stopAllVoiceActions(deleteRecord)
    +        audioMessageHelper.clearTracker()
    +        audioMessageHelper.stopAllVoiceActions(deleteRecord)
         }
     
         private fun handleInitializeVoiceRecorder(attachmentData: ContentAttachmentData) {
    -        voiceMessageHelper.initializeRecorder(attachmentData)
    +        audioMessageHelper.initializeRecorder(attachmentData)
             setState { copy(voiceRecordingUiState = VoiceMessageRecorderView.RecordingUiState.Draft) }
         }
     
         private fun handlePauseRecordingVoiceMessage() {
    -        voiceMessageHelper.pauseRecording()
    +        audioMessageHelper.pauseRecording()
    +    }
    +
    +    private fun handleVoiceWaveformTouchedUp(action: MessageComposerAction.VoiceWaveformTouchedUp) {
    +        audioMessageHelper.movePlaybackTo(action.eventId, action.percentage, action.duration)
    +    }
    +
    +    private fun handleVoiceWaveformMovedTo(action: MessageComposerAction.VoiceWaveformMovedTo) {
    +        audioMessageHelper.movePlaybackTo(action.eventId, action.percentage, action.duration)
    +    }
    +
    +    private fun handleAudioSeekBarMovedTo(action: MessageComposerAction.AudioSeekBarMovedTo) {
    +        audioMessageHelper.movePlaybackTo(action.eventId, action.percentage, action.duration)
         }
     
         private fun handleEntersBackground(composerText: String) {
    +        // Always stop all voice actions. It may be playing in timeline or active recording
    +        val playingAudioContent = audioMessageHelper.stopAllVoiceActions(deleteRecord = false)
    +
             val isVoiceRecording = com.airbnb.mvrx.withState(this) { it.isVoiceRecording }
             if (isVoiceRecording) {
    -            voiceMessageHelper.clearTracker()
                 viewModelScope.launch {
    -                voiceMessageHelper.stopAllVoiceActions(deleteRecord = false)?.toContentAttachmentData()?.let { voiceDraft ->
    +                playingAudioContent?.toContentAttachmentData()?.let { voiceDraft ->
                         val content = voiceDraft.toJsonString()
                         room.saveDraft(UserDraft.Voice(content))
                         setState { copy(sendMode = SendMode.Voice(content)) }
    @@ -885,13 +913,13 @@ class MessageComposerViewModel @AssistedInject constructor(
             }
         }
     
    -    private fun launchSlashCommandFlowSuspendable(block: suspend () -> Unit) {
    +    private fun launchSlashCommandFlowSuspendable(parsedCommand: ParsedCommand, block: suspend () -> Unit) {
             _viewEvents.post(MessageComposerViewEvents.SlashCommandLoading)
             viewModelScope.launch {
                 val event = try {
                     block()
                     popDraft()
    -                MessageComposerViewEvents.SlashCommandResultOk()
    +                MessageComposerViewEvents.SlashCommandResultOk(parsedCommand)
                 } catch (failure: Throwable) {
                     MessageComposerViewEvents.SlashCommandResultError(failure)
                 }
    diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/composer/voice/VoiceMessageRecorderView.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/composer/voice/VoiceMessageRecorderView.kt
    index 9a643796a9..b898aaf114 100644
    --- a/vector/src/main/java/im/vector/app/features/home/room/detail/composer/voice/VoiceMessageRecorderView.kt
    +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/composer/voice/VoiceMessageRecorderView.kt
    @@ -23,12 +23,11 @@ import androidx.constraintlayout.widget.ConstraintLayout
     import dagger.hilt.android.AndroidEntryPoint
     import im.vector.app.BuildConfig
     import im.vector.app.R
    -import im.vector.app.core.extensions.exhaustive
     import im.vector.app.core.hardware.vibrate
     import im.vector.app.core.time.Clock
     import im.vector.app.core.utils.DimensionConverter
     import im.vector.app.databinding.ViewVoiceMessageRecorderBinding
    -import im.vector.app.features.home.room.detail.timeline.helper.VoiceMessagePlaybackTracker
    +import im.vector.app.features.home.room.detail.timeline.helper.AudioMessagePlaybackTracker
     import im.vector.lib.core.utils.timer.CountUpTimer
     import javax.inject.Inject
     import kotlin.math.floor
    @@ -41,7 +40,7 @@ class VoiceMessageRecorderView @JvmOverloads constructor(
             context: Context,
             attrs: AttributeSet? = null,
             defStyleAttr: Int = 0
    -) : ConstraintLayout(context, attrs, defStyleAttr), VoiceMessagePlaybackTracker.Listener {
    +) : ConstraintLayout(context, attrs, defStyleAttr), AudioMessagePlaybackTracker.Listener {
     
         interface Callback {
             fun onVoiceRecordingStarted()
    @@ -53,6 +52,8 @@ class VoiceMessageRecorderView @JvmOverloads constructor(
             fun onDeleteVoiceMessage()
             fun onRecordingLimitReached()
             fun onRecordingWaveformClicked()
    +        fun onVoiceWaveformTouchedUp(percentage: Float, duration: Int)
    +        fun onVoiceWaveformMoved(percentage: Float, duration: Int)
         }
     
         @Inject lateinit var clock: Clock
    @@ -65,6 +66,7 @@ class VoiceMessageRecorderView @JvmOverloads constructor(
         private var recordingTicker: CountUpTimer? = null
         private var lastKnownState: RecordingUiState? = null
         private var dragState: DraggingState = DraggingState.Ignored
    +    private var recordingDuration: Long = 0
     
         init {
             inflate(this.context, R.layout.view_voice_message_recorder, this)
    @@ -95,9 +97,9 @@ class VoiceMessageRecorderView @JvmOverloads constructor(
                 override fun onDeleteVoiceMessage() = callback.onDeleteVoiceMessage()
                 override fun onWaveformClicked() {
                     when (lastKnownState) {
    -                    RecordingUiState.Draft  -> callback.onVoicePlaybackButtonClicked()
                         is RecordingUiState.Recording,
                         is RecordingUiState.Locked -> callback.onRecordingWaveformClicked()
    +                    else                       -> Unit
                     }
                 }
     
    @@ -105,6 +107,18 @@ class VoiceMessageRecorderView @JvmOverloads constructor(
                 override fun onMicButtonDrag(nextDragStateCreator: (DraggingState) -> DraggingState) {
                     onDrag(dragState, newDragState = nextDragStateCreator(dragState))
                 }
    +
    +            override fun onVoiceWaveformTouchedUp(percentage: Float) {
    +                if (lastKnownState == RecordingUiState.Draft) {
    +                    callback.onVoiceWaveformTouchedUp(percentage, recordingDuration.toInt())
    +                }
    +            }
    +
    +            override fun onVoiceWaveformMoved(percentage: Float) {
    +                if (lastKnownState == RecordingUiState.Draft) {
    +                    callback.onVoiceWaveformMoved(percentage, recordingDuration.toInt())
    +                }
    +            }
             })
         }
     
    @@ -119,7 +133,7 @@ class VoiceMessageRecorderView @JvmOverloads constructor(
         fun render(recordingState: RecordingUiState) {
             if (lastKnownState == recordingState) return
             when (recordingState) {
    -            RecordingUiState.Idle      -> {
    +            RecordingUiState.Idle         -> {
                     reset()
                 }
                 is RecordingUiState.Recording -> {
    @@ -137,7 +151,7 @@ class VoiceMessageRecorderView @JvmOverloads constructor(
                         voiceMessageViews.showRecordingLockedViews(recordingState)
                     }, 500)
                 }
    -            RecordingUiState.Draft   -> {
    +            RecordingUiState.Draft        -> {
                     stopRecordingTicker()
                     voiceMessageViews.showDraftViews()
                 }
    @@ -167,7 +181,7 @@ class VoiceMessageRecorderView @JvmOverloads constructor(
                 DraggingState.Ready         -> {
                     // do nothing
                 }
    -        }.exhaustive
    +        }
             dragState = newDragState
         }
     
    @@ -203,20 +217,21 @@ class VoiceMessageRecorderView @JvmOverloads constructor(
         }
     
         private fun stopRecordingTicker() {
    +        recordingDuration = recordingTicker?.elapsedTime() ?: 0
             recordingTicker?.stop()
             recordingTicker = null
         }
     
    -    override fun onUpdate(state: VoiceMessagePlaybackTracker.Listener.State) {
    +    override fun onUpdate(state: AudioMessagePlaybackTracker.Listener.State) {
             when (state) {
    -            is VoiceMessagePlaybackTracker.Listener.State.Recording -> {
    +            is AudioMessagePlaybackTracker.Listener.State.Recording -> {
                     voiceMessageViews.renderRecordingWaveform(state.amplitudeList.toTypedArray())
                 }
    -            is VoiceMessagePlaybackTracker.Listener.State.Playing   -> {
    +            is AudioMessagePlaybackTracker.Listener.State.Playing   -> {
                     voiceMessageViews.renderPlaying(state)
                 }
    -            is VoiceMessagePlaybackTracker.Listener.State.Paused,
    -            is VoiceMessagePlaybackTracker.Listener.State.Idle      -> {
    +            is AudioMessagePlaybackTracker.Listener.State.Paused,
    +            is AudioMessagePlaybackTracker.Listener.State.Idle      -> {
                     voiceMessageViews.renderIdle()
                 }
             }
    diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/composer/voice/VoiceMessageViews.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/composer/voice/VoiceMessageViews.kt
    index 09284ea5fc..0256064af2 100644
    --- a/vector/src/main/java/im/vector/app/features/home/room/detail/composer/voice/VoiceMessageViews.kt
    +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/composer/voice/VoiceMessageViews.kt
    @@ -27,7 +27,6 @@ import androidx.core.view.doOnLayout
     import androidx.core.view.isInvisible
     import androidx.core.view.isVisible
     import androidx.core.view.updateLayoutParams
    -import com.visualizer.amplitude.AudioRecordView
     import im.vector.app.R
     import im.vector.app.core.extensions.setAttributeBackground
     import im.vector.app.core.extensions.setAttributeTintedBackground
    @@ -36,7 +35,9 @@ import im.vector.app.core.utils.DimensionConverter
     import im.vector.app.databinding.ViewVoiceMessageRecorderBinding
     import im.vector.app.features.home.room.detail.composer.voice.VoiceMessageRecorderView.DraggingState
     import im.vector.app.features.home.room.detail.composer.voice.VoiceMessageRecorderView.RecordingUiState
    -import im.vector.app.features.home.room.detail.timeline.helper.VoiceMessagePlaybackTracker
    +import im.vector.app.features.home.room.detail.timeline.helper.AudioMessagePlaybackTracker
    +import im.vector.app.features.themes.ThemeUtils
    +import im.vector.app.features.voice.AudioWaveformView
     
     class VoiceMessageViews(
             private val resources: Resources,
    @@ -59,8 +60,21 @@ class VoiceMessageViews(
                 actions.onDeleteVoiceMessage()
             }
     
    -        views.voicePlaybackWaveform.setOnClickListener {
    -            actions.onWaveformClicked()
    +        views.voicePlaybackWaveform.setOnTouchListener { view, motionEvent ->
    +            when (motionEvent.action) {
    +                MotionEvent.ACTION_DOWN -> {
    +                    actions.onWaveformClicked()
    +                }
    +                MotionEvent.ACTION_UP   -> {
    +                    val percentage = getTouchedPositionPercentage(motionEvent, view)
    +                    actions.onVoiceWaveformTouchedUp(percentage)
    +                }
    +                MotionEvent.ACTION_MOVE -> {
    +                    val percentage = getTouchedPositionPercentage(motionEvent, view)
    +                    actions.onVoiceWaveformMoved(percentage)
    +                }
    +            }
    +            true
             }
     
             views.voicePlaybackControlButton.setOnClickListener {
    @@ -69,6 +83,8 @@ class VoiceMessageViews(
             observeMicButton(actions)
         }
     
    +    private fun getTouchedPositionPercentage(motionEvent: MotionEvent, view: View) = (motionEvent.x / view.width).coerceIn(0f, 1f)
    +
         @SuppressLint("ClickableViewAccessibility")
         private fun observeMicButton(actions: Actions) {
             val draggableStateProcessor = DraggableStateProcessor(resources, dimensionConverter)
    @@ -284,19 +300,23 @@ class VoiceMessageViews(
             hideRecordingViews(RecordingUiState.Idle)
             views.voiceMessageMicButton.isVisible = true
             views.voiceMessageSendButton.isVisible = false
    -        views.voicePlaybackWaveform.post { views.voicePlaybackWaveform.recreate() }
    +        views.voicePlaybackWaveform.post { views.voicePlaybackWaveform.clear() }
         }
     
    -    fun renderPlaying(state: VoiceMessagePlaybackTracker.Listener.State.Playing) {
    +    fun renderPlaying(state: AudioMessagePlaybackTracker.Listener.State.Playing) {
             views.voicePlaybackControlButton.setImageResource(R.drawable.ic_play_pause_pause)
             views.voicePlaybackControlButton.contentDescription = resources.getString(R.string.a11y_pause_voice_message)
             val formattedTimerText = DateUtils.formatElapsedTime((state.playbackTime / 1000).toLong())
             views.voicePlaybackTime.text = formattedTimerText
    +        val waveformColorIdle = ThemeUtils.getColor(views.voicePlaybackWaveform.context, R.attr.vctr_content_quaternary)
    +        val waveformColorPlayed = ThemeUtils.getColor(views.voicePlaybackWaveform.context, R.attr.vctr_content_secondary)
    +        views.voicePlaybackWaveform.updateColors(state.percentage, waveformColorPlayed, waveformColorIdle)
         }
     
         fun renderIdle() {
             views.voicePlaybackControlButton.setImageResource(R.drawable.ic_play_pause_play)
             views.voicePlaybackControlButton.contentDescription = resources.getString(R.string.a11y_play_voice_message)
    +        views.voicePlaybackWaveform.summarize()
         }
     
         fun renderToast(message: String) {
    @@ -327,8 +347,9 @@ class VoiceMessageViews(
     
         fun renderRecordingWaveform(amplitudeList: Array) {
             views.voicePlaybackWaveform.doOnLayout { waveFormView ->
    +            val waveformColor = ThemeUtils.getColor(waveFormView.context, R.attr.vctr_content_quaternary)
                 amplitudeList.iterator().forEach {
    -                (waveFormView as AudioRecordView).update(it)
    +                (waveFormView as AudioWaveformView).add(AudioWaveformView.FFT(it.toFloat(), waveformColor))
                 }
             }
         }
    @@ -349,5 +370,7 @@ class VoiceMessageViews(
             fun onDeleteVoiceMessage()
             fun onWaveformClicked()
             fun onVoicePlaybackButtonClicked()
    +        fun onVoiceWaveformTouchedUp(percentage: Float)
    +        fun onVoiceWaveformMoved(percentage: Float)
         }
     }
    diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/readreceipts/DisplayReadReceiptsController.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/readreceipts/DisplayReadReceiptsController.kt
    index bf88218fa6..c316c556b0 100644
    --- a/vector/src/main/java/im/vector/app/features/home/room/detail/readreceipts/DisplayReadReceiptsController.kt
    +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/readreceipts/DisplayReadReceiptsController.kt
    @@ -31,7 +31,7 @@ import javax.inject.Inject
     class DisplayReadReceiptsController @Inject constructor(private val dateFormatter: VectorDateFormatter,
                                                             private val session: Session,
                                                             private val avatarRender: AvatarRenderer) :
    -    TypedEpoxyController>() {
    +        TypedEpoxyController>() {
     
         var listener: Listener? = null
     
    diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/search/SearchFragment.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/search/SearchFragment.kt
    index 62c142238e..b3543ae579 100644
    --- a/vector/src/main/java/im/vector/app/features/home/room/detail/search/SearchFragment.kt
    +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/search/SearchFragment.kt
    @@ -99,6 +99,7 @@ class SearchFragment @Inject constructor(
                                 title = getString(R.string.search_no_results),
                                 image = ContextCompat.getDrawable(requireContext(), R.drawable.ic_search_no_results))
                     }
    +                else       -> Unit
                 }
             } else {
                 controller.setData(state)
    diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/search/SearchResultController.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/search/SearchResultController.kt
    index 5b1f17cfe2..4f951dfecb 100644
    --- a/vector/src/main/java/im/vector/app/features/home/room/detail/search/SearchResultController.kt
    +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/search/SearchResultController.kt
    @@ -125,7 +125,7 @@ class SearchResultController @Inject constructor(
                         .formattedDate(dateFormatter.format(event.originServerTs, DateFormatKind.MESSAGE_SIMPLE))
                         .spannable(spannable.toEpoxyCharSequence())
                         .sender(eventAndSender.sender
    -                            ?: eventAndSender.event.senderId?.let { session.getRoomMember(it, data.roomId) }?.toMatrixItem())
    +                            ?: eventAndSender.event.senderId?.let { session.roomService().getRoomMember(it, data.roomId) }?.toMatrixItem())
                         .threadDetails(event.threadDetails)
                         .threadSummaryFormatted(displayableEventFormatter.formatThreadSummary(event.threadDetails?.threadSummaryLatestEvent).toString())
                         .areThreadMessagesEnabled(userPreferencesProvider.areThreadMessagesEnabled())
    diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/search/SearchViewModel.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/search/SearchViewModel.kt
    index 7bff76cc36..561023401f 100644
    --- a/vector/src/main/java/im/vector/app/features/home/room/detail/search/SearchViewModel.kt
    +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/search/SearchViewModel.kt
    @@ -25,17 +25,17 @@ import dagger.assisted.AssistedFactory
     import dagger.assisted.AssistedInject
     import im.vector.app.core.di.MavericksAssistedViewModelFactory
     import im.vector.app.core.di.hiltMavericksViewModelFactory
    -import im.vector.app.core.extensions.exhaustive
     import im.vector.app.core.platform.VectorViewModel
     import kotlinx.coroutines.CancellationException
     import kotlinx.coroutines.Job
     import kotlinx.coroutines.launch
     import org.matrix.android.sdk.api.session.Session
    +import org.matrix.android.sdk.api.session.getRoom
     import org.matrix.android.sdk.api.session.search.SearchResult
     
     class SearchViewModel @AssistedInject constructor(
             @Assisted private val initialState: SearchViewState,
    -        session: Session
    +        private val session: Session
     ) : VectorViewModel(initialState) {
     
         private val room = session.getRoom(initialState.roomId)
    @@ -56,7 +56,7 @@ class SearchViewModel @AssistedInject constructor(
                 is SearchAction.SearchWith -> handleSearchWith(action)
                 is SearchAction.LoadMore   -> handleLoadMore()
                 is SearchAction.Retry      -> handleRetry()
    -        }.exhaustive
    +        }
         }
     
         private fun handleSearchWith(action: SearchAction.SearchWith) {
    @@ -101,8 +101,9 @@ class SearchViewModel @AssistedInject constructor(
     
             currentTask = viewModelScope.launch {
                 try {
    -                val result = room.search(
    +                val result = session.searchService().search(
                             searchTerm = state.searchTerm,
    +                        roomId = initialState.roomId,
                             nextBatch = nextBatch,
                             orderByRecent = true,
                             beforeLimit = 0,
    diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/TimelineEventController.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/TimelineEventController.kt
    index fb47fb5136..8cea57399a 100644
    --- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/TimelineEventController.kt
    +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/TimelineEventController.kt
    @@ -57,6 +57,7 @@ import im.vector.app.features.home.room.detail.timeline.item.ReactionsSummaryEve
     import im.vector.app.features.home.room.detail.timeline.item.ReadReceiptData
     import im.vector.app.features.home.room.detail.timeline.item.ReadReceiptsItem
     import im.vector.app.features.home.room.detail.timeline.url.PreviewUrlRetriever
    +import im.vector.app.features.media.AttachmentData
     import im.vector.app.features.media.ImageContentRenderer
     import im.vector.app.features.media.VideoContentRenderer
     import im.vector.app.features.settings.VectorPreferences
    @@ -127,7 +128,11 @@ class TimelineEventController @Inject constructor(private val dateFormatter: Vec
             fun onEventVisible(event: TimelineEvent)
             fun onRoomCreateLinkClicked(url: String)
             fun onEncryptedMessageClicked(informationData: MessageInformationData, view: View)
    -        fun onImageMessageClicked(messageImageContent: MessageImageInfoContent, mediaData: ImageContentRenderer.Data, view: View)
    +        fun onImageMessageClicked(messageImageContent: MessageImageInfoContent,
    +                                  mediaData: ImageContentRenderer.Data,
    +                                  view: View,
    +                                  inMemory: List)
    +
             fun onVideoMessageClicked(messageVideoContent: MessageVideoContent, mediaData: VideoContentRenderer.Data, view: View)
     
             //        fun onFileMessageClicked(eventId: String, messageFileContent: MessageFileContent)
    @@ -141,6 +146,10 @@ class TimelineEventController @Inject constructor(private val dateFormatter: Vec
             fun getPreviewUrlRetriever(): PreviewUrlRetriever
     
             fun onVoiceControlButtonClicked(eventId: String, messageAudioContent: MessageAudioContent)
    +        fun onVoiceWaveformTouchedUp(eventId: String, duration: Int, percentage: Float)
    +        fun onVoiceWaveformMovedTo(eventId: String, duration: Int, percentage: Float)
    +
    +        fun onAudioSeekBarMovedTo(eventId: String, duration: Int, percentage: Float)
     
             fun onAddMoreReaction(event: TimelineEvent)
         }
    diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/model/rest/EncryptedBodyFileInfo.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/action/LocationUiData.kt
    similarity index 58%
    rename from matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/model/rest/EncryptedBodyFileInfo.kt
    rename to vector/src/main/java/im/vector/app/features/home/room/detail/timeline/action/LocationUiData.kt
    index 90f97b65eb..c50c649221 100644
    --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/model/rest/EncryptedBodyFileInfo.kt
    +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/action/LocationUiData.kt
    @@ -1,5 +1,5 @@
     /*
    - * Copyright 2020 The Matrix.org Foundation C.I.C.
    + * 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.
    @@ -13,17 +13,16 @@
      * See the License for the specific language governing permissions and
      * limitations under the License.
      */
    -package org.matrix.android.sdk.internal.crypto.model.rest
     
    -import org.matrix.olm.OlmPkMessage
    +package im.vector.app.features.home.room.detail.timeline.action
    +
    +import im.vector.app.features.home.room.detail.timeline.helper.LocationPinProvider
     
     /**
    - * Build from a OlmPkMessage object
    - *
    - * @param olmPkMessage OlmPkMessage
    + * Data used to display Location data in the message bottom sheet
      */
    -class EncryptedBodyFileInfo(olmPkMessage: OlmPkMessage) {
    -    var ciphertext = olmPkMessage.mCipherText
    -    var mac = olmPkMessage.mMac
    -    var ephemeral = olmPkMessage.mEphemeralKey
    -}
    +data class LocationUiData(
    +        val locationUrl: String,
    +        val locationOwnerId: String?,
    +        val locationPinProvider: LocationPinProvider,
    +)
    diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/action/MessageActionsEpoxyController.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/action/MessageActionsEpoxyController.kt
    index 27937047a5..41916c609d 100644
    --- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/action/MessageActionsEpoxyController.kt
    +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/action/MessageActionsEpoxyController.kt
    @@ -43,10 +43,11 @@ import im.vector.app.features.location.INITIAL_MAP_ZOOM_IN_TIMELINE
     import im.vector.app.features.location.UrlMapProvider
     import im.vector.app.features.location.toLocationData
     import im.vector.app.features.media.ImageContentRenderer
    +import im.vector.app.features.settings.VectorPreferences
     import im.vector.lib.core.utils.epoxy.charsequence.toEpoxyCharSequence
     import org.matrix.android.sdk.api.extensions.orFalse
    -import org.matrix.android.sdk.api.extensions.orTrue
     import org.matrix.android.sdk.api.failure.Failure
    +import org.matrix.android.sdk.api.session.events.model.isLocationMessage
     import org.matrix.android.sdk.api.session.events.model.toModel
     import org.matrix.android.sdk.api.session.room.model.message.MessageLocationContent
     import org.matrix.android.sdk.api.session.room.send.SendState
    @@ -64,6 +65,7 @@ class MessageActionsEpoxyController @Inject constructor(
             private val errorFormatter: ErrorFormatter,
             private val spanUtils: SpanUtils,
             private val eventDetailsFormatter: EventDetailsFormatter,
    +        private val vectorPreferences: VectorPreferences,
             private val dateFormatter: VectorDateFormatter,
             private val urlMapProvider: UrlMapProvider,
             private val locationPinProvider: LocationPinProvider
    @@ -78,12 +80,7 @@ class MessageActionsEpoxyController @Inject constructor(
             val formattedDate = dateFormatter.format(date, DateFormatKind.MESSAGE_DETAIL)
             val body = state.messageBody.linkify(host.listener)
             val bindingOptions = spanUtils.getBindingOptions(body)
    -
    -        val locationContent = state.timelineEvent()?.root?.getClearContent()
    -                ?.toModel(catchError = true)
    -        val locationUrl = locationContent?.toLocationData()
    -                ?.let { urlMapProvider.buildStaticMapUrl(it, INITIAL_MAP_ZOOM_IN_TIMELINE, 1200, 800) }
    -        val locationOwnerId = if (locationContent?.isSelfLocation().orTrue()) state.informationData.matrixItem.id else null
    +        val locationUiData = buildLocationUiData(state)
     
             bottomSheetMessagePreviewItem {
                 id("preview")
    @@ -97,9 +94,7 @@ class MessageActionsEpoxyController @Inject constructor(
                 body(body.toEpoxyCharSequence())
                 bodyDetails(host.eventDetailsFormatter.format(state.timelineEvent()?.root)?.toEpoxyCharSequence())
                 time(formattedDate)
    -            locationUrl(locationUrl)
    -            locationPinProvider(host.locationPinProvider)
    -            locationOwnerId(locationOwnerId)
    +            locationUiData(locationUiData)
             }
     
             // Send state
    @@ -187,6 +182,8 @@ class MessageActionsEpoxyController @Inject constructor(
                         id("separator_$index")
                     }
                 } else {
    +                val showBetaLabel = action.shouldShowBetaLabel()
    +
                     bottomSheetActionItem {
                         id("action_$index")
                         iconRes(action.iconResId)
    @@ -195,6 +192,7 @@ class MessageActionsEpoxyController @Inject constructor(
                         expanded(state.expendedReportContentMenu)
                         listener { host.listener?.didSelectMenuAction(action) }
                         destructive(action.destructive)
    +                    showBetaLabel(showBetaLabel)
                     }
     
                     if (action is EventSharedAction.ReportContent && state.expendedReportContentMenu) {
    @@ -217,6 +215,26 @@ class MessageActionsEpoxyController @Inject constructor(
             }
         }
     
    +    private fun buildLocationUiData(state: MessageActionState): LocationUiData? {
    +        if (state.timelineEvent()?.root?.isLocationMessage() != true) return null
    +
    +        val locationContent = state.timelineEvent()?.root?.getClearContent().toModel(catchError = true)
    +                ?: return null
    +        val locationUrl = locationContent.toLocationData()
    +                ?.let { urlMapProvider.buildStaticMapUrl(it, INITIAL_MAP_ZOOM_IN_TIMELINE, 1200, 800) }
    +                ?: return null
    +        val locationOwnerId = if (locationContent.isSelfLocation()) state.informationData.matrixItem.id else null
    +
    +        return LocationUiData(
    +                locationUrl = locationUrl,
    +                locationOwnerId = locationOwnerId,
    +                locationPinProvider = locationPinProvider,
    +        )
    +    }
    +
    +    private fun EventSharedAction.shouldShowBetaLabel(): Boolean =
    +            this is EventSharedAction.ReplyInThread && !vectorPreferences.areThreadMessagesEnabled()
    +
         interface MessageActionsEpoxyControllerListener : TimelineEventController.UrlClickCallback {
             fun didSelectMenuAction(eventAction: EventSharedAction)
         }
    diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/action/MessageActionsViewModel.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/action/MessageActionsViewModel.kt
    index bd4e93b25d..df2a1fbe81 100644
    --- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/action/MessageActionsViewModel.kt
    +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/action/MessageActionsViewModel.kt
    @@ -48,6 +48,7 @@ import org.matrix.android.sdk.api.session.events.model.isAttachmentMessage
     import org.matrix.android.sdk.api.session.events.model.isTextMessage
     import org.matrix.android.sdk.api.session.events.model.isThread
     import org.matrix.android.sdk.api.session.events.model.toModel
    +import org.matrix.android.sdk.api.session.getRoom
     import org.matrix.android.sdk.api.session.room.model.message.MessageContent
     import org.matrix.android.sdk.api.session.room.model.message.MessageFormat
     import org.matrix.android.sdk.api.session.room.model.message.MessagePollContent
    @@ -69,16 +70,16 @@ import org.matrix.android.sdk.flow.unwrap
     /**
      * Information related to an event and used to display preview in contextual bottom sheet.
      */
    -class MessageActionsViewModel @AssistedInject constructor(@Assisted
    -                                                          private val initialState: MessageActionState,
    -                                                          private val eventHtmlRenderer: Lazy,
    -                                                          private val htmlCompressor: VectorHtmlCompressor,
    -                                                          private val session: Session,
    -                                                          private val noticeEventFormatter: NoticeEventFormatter,
    -                                                          private val errorFormatter: ErrorFormatter,
    -                                                          private val stringProvider: StringProvider,
    -                                                          private val pillsPostProcessorFactory: PillsPostProcessor.Factory,
    -                                                          private val vectorPreferences: VectorPreferences
    +class MessageActionsViewModel @AssistedInject constructor(
    +        @Assisted private val initialState: MessageActionState,
    +        private val eventHtmlRenderer: Lazy,
    +        private val htmlCompressor: VectorHtmlCompressor,
    +        private val session: Session,
    +        private val noticeEventFormatter: NoticeEventFormatter,
    +        private val errorFormatter: ErrorFormatter,
    +        private val stringProvider: StringProvider,
    +        private val pillsPostProcessorFactory: PillsPostProcessor.Factory,
    +        private val vectorPreferences: VectorPreferences
     ) : VectorViewModel(initialState) {
     
         private val informationData = initialState.informationData
    @@ -450,7 +451,12 @@ class MessageActionsViewModel @AssistedInject constructor(@Assisted
         private fun canReplyInThread(event: TimelineEvent,
                                      messageContent: MessageContent?,
                                      actionPermissions: ActionPermissions): Boolean {
    -        if (!vectorPreferences.areThreadMessagesEnabled()) return false
    +        // We let reply in thread visible even if threads are not enabled, with an enhanced flow to attract users
    +//        if (!vectorPreferences.areThreadMessagesEnabled()) return false
    +        // Disable beta prompt if the homeserver do not support threads
    +        if (!vectorPreferences.areThreadMessagesEnabled() &&
    +                !session.homeServerCapabilitiesService().getHomeServerCapabilities().canUseThreading) return false
    +
             if (initialState.isFromThreadTimeline) return false
             if (event.root.isThread()) return false
             if (event.root.getClearType() != EventType.MESSAGE &&
    diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/edithistory/ViewEditHistoryEpoxyController.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/edithistory/ViewEditHistoryEpoxyController.kt
    index 1dad6cc4a7..63a34fe713 100644
    --- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/edithistory/ViewEditHistoryEpoxyController.kt
    +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/edithistory/ViewEditHistoryEpoxyController.kt
    @@ -18,8 +18,9 @@ package im.vector.app.features.home.room.detail.timeline.edithistory
     import android.text.Spannable
     import com.airbnb.epoxy.TypedEpoxyController
     import com.airbnb.mvrx.Fail
    -import com.airbnb.mvrx.Incomplete
    +import com.airbnb.mvrx.Loading
     import com.airbnb.mvrx.Success
    +import com.airbnb.mvrx.Uninitialized
     import im.vector.app.R
     import im.vector.app.core.date.DateFormatKind
     import im.vector.app.core.date.VectorDateFormatter
    @@ -37,7 +38,7 @@ 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.room.model.message.MessageTextContent
     import org.matrix.android.sdk.api.util.ContentUtils.extractUsefulTextFromReply
    -import org.matrix.android.sdk.internal.session.room.send.TextContent
    +import org.matrix.android.sdk.api.util.TextContent
     import java.util.Calendar
     import javax.inject.Inject
     
    @@ -54,18 +55,19 @@ class ViewEditHistoryEpoxyController @Inject constructor(
         override fun buildModels(state: ViewEditHistoryViewState) {
             val host = this
             when (state.editList) {
    -            is Incomplete -> {
    +            Uninitialized,
    +            is Loading -> {
                     genericLoaderItem {
                         id("Spinner")
                     }
                 }
    -            is Fail       -> {
    +            is Fail    -> {
                     genericFooterItem {
                         id("failure")
                         text(host.stringProvider.getString(R.string.unknown_error).toEpoxyCharSequence())
                     }
                 }
    -            is Success    -> {
    +            is Success -> {
                     state.editList()?.let { renderEvents(it, state.isOriginalAReply) }
                 }
             }
    diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/edithistory/ViewEditHistoryViewModel.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/edithistory/ViewEditHistoryViewModel.kt
    index 9abc67e41f..11d7979f21 100644
    --- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/edithistory/ViewEditHistoryViewModel.kt
    +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/edithistory/ViewEditHistoryViewModel.kt
    @@ -30,8 +30,9 @@ import im.vector.app.core.platform.VectorViewModel
     import kotlinx.coroutines.launch
     import org.matrix.android.sdk.api.session.Session
     import org.matrix.android.sdk.api.session.crypto.MXCryptoError
    +import org.matrix.android.sdk.api.session.crypto.model.OlmDecryptionResult
     import org.matrix.android.sdk.api.session.events.model.isReply
    -import org.matrix.android.sdk.internal.crypto.algorithms.olm.OlmDecryptionResult
    +import org.matrix.android.sdk.api.session.getRoom
     import timber.log.Timber
     import java.util.UUID
     
    diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/edithistory/ViewEditHistoryViewState.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/edithistory/ViewEditHistoryViewState.kt
    index 61a400d875..dd344c4c82 100644
    --- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/edithistory/ViewEditHistoryViewState.kt
    +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/edithistory/ViewEditHistoryViewState.kt
    @@ -27,7 +27,7 @@ data class ViewEditHistoryViewState(
             val roomId: String,
             val isOriginalAReply: Boolean = false,
             val editList: Async> = Uninitialized) :
    -    MavericksState {
    +        MavericksState {
     
         constructor(args: TimelineEventFragmentArgs) : this(roomId = args.roomId, eventId = args.eventId)
     }
    diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/EncryptedItemFactory.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/EncryptedItemFactory.kt
    index 2b04600af2..dd91f00ab9 100644
    --- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/EncryptedItemFactory.kt
    +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/EncryptedItemFactory.kt
    @@ -32,8 +32,8 @@ import me.gujun.android.span.image
     import me.gujun.android.span.span
     import org.matrix.android.sdk.api.session.crypto.MXCryptoError
     import org.matrix.android.sdk.api.session.events.model.EventType
    +import org.matrix.android.sdk.api.session.events.model.content.EncryptedEventContent
     import org.matrix.android.sdk.api.session.events.model.toModel
    -import org.matrix.android.sdk.internal.crypto.model.event.EncryptedEventContent
     import javax.inject.Inject
     
     // This class handles timeline events who haven't been successfully decrypted
    diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/EncryptionItemFactory.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/EncryptionItemFactory.kt
    index 0cb86a5c1c..0cbd92f525 100644
    --- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/EncryptionItemFactory.kt
    +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/EncryptionItemFactory.kt
    @@ -24,11 +24,12 @@ import im.vector.app.features.home.room.detail.timeline.helper.MessageInformatio
     import im.vector.app.features.home.room.detail.timeline.helper.MessageItemAttributesFactory
     import im.vector.app.features.home.room.detail.timeline.item.StatusTileTimelineItem
     import im.vector.app.features.home.room.detail.timeline.item.StatusTileTimelineItem_
    +import org.matrix.android.sdk.api.crypto.MXCRYPTO_ALGORITHM_MEGOLM
     import org.matrix.android.sdk.api.extensions.orFalse
     import org.matrix.android.sdk.api.session.Session
    +import org.matrix.android.sdk.api.session.events.model.content.EncryptionEventContent
     import org.matrix.android.sdk.api.session.events.model.toModel
    -import org.matrix.android.sdk.internal.crypto.MXCRYPTO_ALGORITHM_MEGOLM
    -import org.matrix.android.sdk.internal.crypto.model.event.EncryptionEventContent
    +import org.matrix.android.sdk.api.session.getRoomSummary
     import javax.inject.Inject
     
     class EncryptionItemFactory @Inject constructor(
    diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/LiveLocationMessageItemFactory.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/LiveLocationMessageItemFactory.kt
    new file mode 100644
    index 0000000000..c417038935
    --- /dev/null
    +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/LiveLocationMessageItemFactory.kt
    @@ -0,0 +1,66 @@
    +/*
    + * Copyright (c) 2022 New Vector Ltd
    + *
    + * Licensed under the Apache License, Version 2.0 (the "License");
    + * you may not use this file except in compliance with the License.
    + * You may obtain a copy of the License at
    + *
    + *     http://www.apache.org/licenses/LICENSE-2.0
    + *
    + * Unless required by applicable law or agreed to in writing, software
    + * distributed under the License is distributed on an "AS IS" BASIS,
    + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    + * See the License for the specific language governing permissions and
    + * limitations under the License.
    + */
    +
    +package im.vector.app.features.home.room.detail.timeline.factory
    +
    +import im.vector.app.core.epoxy.VectorEpoxyModel
    +import im.vector.app.core.utils.DimensionConverter
    +import im.vector.app.features.home.room.detail.timeline.helper.AvatarSizeProvider
    +import im.vector.app.features.home.room.detail.timeline.helper.TimelineMediaSizeProvider
    +import im.vector.app.features.home.room.detail.timeline.item.AbsMessageItem
    +import im.vector.app.features.home.room.detail.timeline.item.MessageLiveLocationStartItem
    +import im.vector.app.features.home.room.detail.timeline.item.MessageLiveLocationStartItem_
    +import org.matrix.android.sdk.api.extensions.orFalse
    +import org.matrix.android.sdk.api.session.room.model.livelocation.LiveLocationBeaconContent
    +import javax.inject.Inject
    +
    +class LiveLocationMessageItemFactory @Inject constructor(
    +        private val dimensionConverter: DimensionConverter,
    +        private val timelineMediaSizeProvider: TimelineMediaSizeProvider,
    +        private val avatarSizeProvider: AvatarSizeProvider,
    +) {
    +
    +    fun create(
    +            liveLocationContent: LiveLocationBeaconContent,
    +            highlight: Boolean,
    +            attributes: AbsMessageItem.Attributes,
    +    ): VectorEpoxyModel<*>? {
    +        // TODO handle location received and stopped states
    +        return when {
    +            isLiveRunning(liveLocationContent) -> buildStartLiveItem(highlight, attributes)
    +            else                               -> null
    +        }
    +    }
    +
    +    private fun isLiveRunning(liveLocationContent: LiveLocationBeaconContent): Boolean {
    +        return liveLocationContent.isLive.orFalse() && liveLocationContent.hasTimedOut.not()
    +    }
    +
    +    private fun buildStartLiveItem(
    +            highlight: Boolean,
    +            attributes: AbsMessageItem.Attributes,
    +    ): MessageLiveLocationStartItem {
    +        val width = timelineMediaSizeProvider.getMaxSize().first
    +        val height = dimensionConverter.dpToPx(MessageItemFactory.MESSAGE_LOCATION_ITEM_HEIGHT_IN_DP)
    +
    +        return MessageLiveLocationStartItem_()
    +                .attributes(attributes)
    +                .mapWidth(width)
    +                .mapHeight(height)
    +                .highlighted(highlight)
    +                .leftGuideline(avatarSizeProvider.leftGuideline)
    +    }
    +}
    diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/MergedHeaderItemFactory.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/MergedHeaderItemFactory.kt
    index 76ed024370..3370473493 100644
    --- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/MergedHeaderItemFactory.kt
    +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/MergedHeaderItemFactory.kt
    @@ -32,16 +32,17 @@ import im.vector.app.features.home.room.detail.timeline.item.MergedRoomCreationI
     import im.vector.app.features.home.room.detail.timeline.item.MergedSimilarEventsItem
     import im.vector.app.features.home.room.detail.timeline.item.MergedSimilarEventsItem_
     import im.vector.app.features.home.room.detail.timeline.tools.createLinkMovementMethod
    +import org.matrix.android.sdk.api.crypto.MXCRYPTO_ALGORITHM_MEGOLM
     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.EventType
    +import org.matrix.android.sdk.api.session.events.model.content.EncryptionEventContent
     import org.matrix.android.sdk.api.session.events.model.toModel
    +import org.matrix.android.sdk.api.session.getRoom
     import org.matrix.android.sdk.api.session.room.model.PowerLevelsContent
     import org.matrix.android.sdk.api.session.room.model.create.RoomCreateContent
     import org.matrix.android.sdk.api.session.room.powerlevels.PowerLevelsHelper
     import org.matrix.android.sdk.api.session.room.timeline.TimelineEvent
    -import org.matrix.android.sdk.internal.crypto.MXCRYPTO_ALGORITHM_MEGOLM
    -import org.matrix.android.sdk.internal.crypto.model.event.EncryptionEventContent
     import javax.inject.Inject
     
     class MergedHeaderItemFactory @Inject constructor(private val activeSessionHolder: ActiveSessionHolder,
    diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/MessageItemFactory.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/MessageItemFactory.kt
    index 3189954e20..186b34dc29 100644
    --- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/MessageItemFactory.kt
    +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/MessageItemFactory.kt
    @@ -34,6 +34,7 @@ import im.vector.app.core.resources.StringProvider
     import im.vector.app.core.utils.DimensionConverter
     import im.vector.app.core.utils.containsOnlyEmojis
     import im.vector.app.features.home.room.detail.timeline.TimelineEventController
    +import im.vector.app.features.home.room.detail.timeline.helper.AudioMessagePlaybackTracker
     import im.vector.app.features.home.room.detail.timeline.helper.AvatarSizeProvider
     import im.vector.app.features.home.room.detail.timeline.helper.ContentDownloadStateTrackerBinder
     import im.vector.app.features.home.room.detail.timeline.helper.ContentUploadStateTrackerBinder
    @@ -41,8 +42,9 @@ import im.vector.app.features.home.room.detail.timeline.helper.LocationPinProvid
     import im.vector.app.features.home.room.detail.timeline.helper.MessageInformationDataFactory
     import im.vector.app.features.home.room.detail.timeline.helper.MessageItemAttributesFactory
     import im.vector.app.features.home.room.detail.timeline.helper.TimelineMediaSizeProvider
    -import im.vector.app.features.home.room.detail.timeline.helper.VoiceMessagePlaybackTracker
     import im.vector.app.features.home.room.detail.timeline.item.AbsMessageItem
    +import im.vector.app.features.home.room.detail.timeline.item.MessageAudioItem
    +import im.vector.app.features.home.room.detail.timeline.item.MessageAudioItem_
     import im.vector.app.features.home.room.detail.timeline.item.MessageFileItem
     import im.vector.app.features.home.room.detail.timeline.item.MessageFileItem_
     import im.vector.app.features.home.room.detail.timeline.item.MessageImageVideoItem
    @@ -56,7 +58,12 @@ import im.vector.app.features.home.room.detail.timeline.item.MessageVoiceItem
     import im.vector.app.features.home.room.detail.timeline.item.MessageVoiceItem_
     import im.vector.app.features.home.room.detail.timeline.item.PollItem
     import im.vector.app.features.home.room.detail.timeline.item.PollItem_
    -import im.vector.app.features.home.room.detail.timeline.item.PollOptionViewState
    +import im.vector.app.features.home.room.detail.timeline.item.PollOptionViewState.PollEnded
    +import im.vector.app.features.home.room.detail.timeline.item.PollOptionViewState.PollReady
    +import im.vector.app.features.home.room.detail.timeline.item.PollOptionViewState.PollSending
    +import im.vector.app.features.home.room.detail.timeline.item.PollOptionViewState.PollUndisclosed
    +import im.vector.app.features.home.room.detail.timeline.item.PollOptionViewState.PollVoted
    +import im.vector.app.features.home.room.detail.timeline.item.PollResponseData
     import im.vector.app.features.home.room.detail.timeline.item.RedactedMessageItem
     import im.vector.app.features.home.room.detail.timeline.item.RedactedMessageItem_
     import im.vector.app.features.home.room.detail.timeline.item.VerificationRequestItem
    @@ -73,15 +80,25 @@ import im.vector.app.features.location.UrlMapProvider
     import im.vector.app.features.location.toLocationData
     import im.vector.app.features.media.ImageContentRenderer
     import im.vector.app.features.media.VideoContentRenderer
    +import im.vector.app.features.poll.PollState
    +import im.vector.app.features.poll.PollState.Ended
    +import im.vector.app.features.poll.PollState.Ready
    +import im.vector.app.features.poll.PollState.Sending
    +import im.vector.app.features.poll.PollState.Undisclosed
    +import im.vector.app.features.poll.PollState.Voted
     import im.vector.app.features.settings.VectorPreferences
    +import im.vector.app.features.voice.AudioWaveformView
     import im.vector.lib.core.utils.epoxy.charsequence.toEpoxyCharSequence
     import me.gujun.android.span.span
     import org.matrix.android.sdk.api.MatrixUrls.isMxcUrl
     import org.matrix.android.sdk.api.extensions.orFalse
     import org.matrix.android.sdk.api.session.Session
    +import org.matrix.android.sdk.api.session.crypto.attachments.toElementToDecrypt
     import org.matrix.android.sdk.api.session.events.model.RelationType
    +import org.matrix.android.sdk.api.session.events.model.content.EncryptedEventContent
     import org.matrix.android.sdk.api.session.events.model.isThread
     import org.matrix.android.sdk.api.session.events.model.toModel
    +import org.matrix.android.sdk.api.session.room.model.livelocation.LiveLocationBeaconContent
     import org.matrix.android.sdk.api.session.room.model.message.MessageAudioContent
     import org.matrix.android.sdk.api.session.room.model.message.MessageContent
     import org.matrix.android.sdk.api.session.room.model.message.MessageContentWithFormattedBody
    @@ -95,15 +112,13 @@ import org.matrix.android.sdk.api.session.room.model.message.MessageTextContent
     import org.matrix.android.sdk.api.session.room.model.message.MessageType
     import org.matrix.android.sdk.api.session.room.model.message.MessageVerificationRequestContent
     import org.matrix.android.sdk.api.session.room.model.message.MessageVideoContent
    +import org.matrix.android.sdk.api.session.room.model.message.PollAnswer
     import org.matrix.android.sdk.api.session.room.model.message.PollType
    -import org.matrix.android.sdk.api.session.room.model.message.getFileName
     import org.matrix.android.sdk.api.session.room.model.message.getFileUrl
     import org.matrix.android.sdk.api.session.room.model.message.getThumbnailUrl
     import org.matrix.android.sdk.api.session.room.timeline.getLastMessageContent
    +import org.matrix.android.sdk.api.settings.LightweightSettingsStorage
     import org.matrix.android.sdk.api.util.MimeTypes
    -import org.matrix.android.sdk.internal.crypto.attachments.toElementToDecrypt
    -import org.matrix.android.sdk.internal.crypto.model.event.EncryptedEventContent
    -import org.matrix.android.sdk.internal.database.lightweight.LightweightSettingsStorage
     import javax.inject.Inject
     
     class MessageItemFactory @Inject constructor(
    @@ -127,10 +142,11 @@ class MessageItemFactory @Inject constructor(
             private val lightweightSettingsStorage: LightweightSettingsStorage,
             private val spanUtils: SpanUtils,
             private val session: Session,
    -        private val voiceMessagePlaybackTracker: VoiceMessagePlaybackTracker,
    +        private val audioMessagePlaybackTracker: AudioMessagePlaybackTracker,
             private val locationPinProvider: LocationPinProvider,
             private val vectorPreferences: VectorPreferences,
             private val urlMapProvider: UrlMapProvider,
    +        private val liveLocationMessageItemFactory: LiveLocationMessageItemFactory,
     ) {
     
         // TODO inject this properly?
    @@ -179,8 +195,8 @@ class MessageItemFactory @Inject constructor(
             // always hide summary when we are on thread timeline
             val attributes = messageItemAttributesFactory.create(messageContent, informationData, callback, params.reactionsSummaryEvents, threadDetails)
     
    -//        val all = event.root.toContent()
    -//        val ev = all.toModel()
    +        //        val all = event.root.toContent()
    +        //        val ev = all.toModel()
             val messageItem = when (messageContent) {
                 is MessageEmoteContent               -> buildEmoteMessageItem(messageContent, informationData, highlight, callback, attributes)
                 is MessageTextContent                -> buildItemForTextContent(messageContent, informationData, highlight, callback, attributes)
    @@ -188,13 +204,7 @@ class MessageItemFactory @Inject constructor(
                 is MessageNoticeContent              -> buildNoticeMessageItem(messageContent, informationData, highlight, callback, attributes)
                 is MessageVideoContent               -> buildVideoMessageItem(messageContent, informationData, highlight, callback, attributes)
                 is MessageFileContent                -> buildFileMessageItem(messageContent, highlight, attributes)
    -            is MessageAudioContent               -> {
    -                if (messageContent.voiceMessageIndicator != null) {
    -                    buildVoiceMessageItem(params, messageContent, informationData, highlight, attributes)
    -                } else {
    -                    buildAudioMessageItem(messageContent, informationData, highlight, attributes)
    -                }
    -            }
    +            is MessageAudioContent               -> buildAudioContent(params, messageContent, informationData, highlight, attributes)
                 is MessageVerificationRequestContent -> buildVerificationRequestMessageItem(messageContent, informationData, highlight, callback, attributes)
                 is MessagePollContent                -> buildPollItem(messageContent, informationData, highlight, callback, attributes)
                 is MessageLocationContent            -> {
    @@ -204,6 +214,7 @@ class MessageItemFactory @Inject constructor(
                         buildMessageTextItem(messageContent.body, false, informationData, highlight, callback, attributes)
                     }
                 }
    +            is LiveLocationBeaconContent         -> liveLocationMessageItemFactory.create(messageContent, highlight, attributes)
                 else                                 -> buildNotHandledMessageItem(messageContent, informationData, highlight, callback, attributes)
             }
             return messageItem?.apply {
    @@ -211,12 +222,14 @@ class MessageItemFactory @Inject constructor(
             }
         }
     
    -    private fun buildLocationItem(locationContent: MessageLocationContent,
    -                                  informationData: MessageInformationData,
    -                                  highlight: Boolean,
    -                                  attributes: AbsMessageItem.Attributes): MessageLocationItem? {
    +    private fun buildLocationItem(
    +            locationContent: MessageLocationContent,
    +            informationData: MessageInformationData,
    +            highlight: Boolean,
    +            attributes: AbsMessageItem.Attributes,
    +    ): MessageLocationItem? {
             val width = timelineMediaSizeProvider.getMaxSize().first
    -        val height = dimensionConverter.dpToPx(200)
    +        val height = dimensionConverter.dpToPx(MESSAGE_LOCATION_ITEM_HEIGHT_IN_DP)
     
             val locationUrl = locationContent.toLocationData()?.let {
                 urlMapProvider.buildStaticMapUrl(it, INITIAL_MAP_ZOOM_IN_TIMELINE, width, height)
    @@ -235,75 +248,26 @@ class MessageItemFactory @Inject constructor(
                     .leftGuideline(avatarSizeProvider.leftGuideline)
         }
     
    -    private fun buildPollItem(pollContent: MessagePollContent,
    -                              informationData: MessageInformationData,
    -                              highlight: Boolean,
    -                              callback: TimelineEventController.Callback?,
    -                              attributes: AbsMessageItem.Attributes): PollItem? {
    -        val optionViewStates = mutableListOf()
    -
    +    private fun buildPollItem(
    +            pollContent: MessagePollContent,
    +            informationData: MessageInformationData,
    +            highlight: Boolean,
    +            callback: TimelineEventController.Callback?,
    +            attributes: AbsMessageItem.Attributes,
    +    ): PollItem {
             val pollResponseSummary = informationData.pollResponseAggregatedSummary
    -        val isEnded = pollResponseSummary?.isClosed.orFalse()
    -        val didUserVoted = pollResponseSummary?.myVote?.isNotEmpty().orFalse()
    -        val winnerVoteCount = pollResponseSummary?.winnerVoteCount
    -        val isPollSent = informationData.sendState.isSent()
    -        val isPollUndisclosed = pollContent.getBestPollCreationInfo()?.kind == PollType.UNDISCLOSED_UNSTABLE
    -
    -        val totalVotesText = (pollResponseSummary?.totalVotes ?: 0).let {
    -            when {
    -                isEnded           -> stringProvider.getQuantityString(R.plurals.poll_total_vote_count_after_ended, it, it)
    -                isPollUndisclosed -> ""
    -                didUserVoted      -> stringProvider.getQuantityString(R.plurals.poll_total_vote_count_before_ended_and_voted, it, it)
    -                else              -> if (it == 0) {
    -                    stringProvider.getString(R.string.poll_no_votes_cast)
    -                } else {
    -                    stringProvider.getQuantityString(R.plurals.poll_total_vote_count_before_ended_and_not_voted, it, it)
    -                }
    -            }
    -        }
    -
    -        pollContent.getBestPollCreationInfo()?.answers?.forEach { option ->
    -            val voteSummary = pollResponseSummary?.votes?.get(option.id)
    -            val isMyVote = pollResponseSummary?.myVote == option.id
    -            val voteCount = voteSummary?.total ?: 0
    -            val votePercentage = voteSummary?.percentage ?: 0.0
    -            val optionId = option.id ?: ""
    -            val optionAnswer = option.getBestAnswer() ?: ""
    -
    -            optionViewStates.add(
    -                    if (!isPollSent) {
    -                        // Poll event is not send yet. Disable option.
    -                        PollOptionViewState.PollSending(optionId, optionAnswer)
    -                    } else if (isEnded) {
    -                        // Poll is ended. Disable option, show votes and mark the winner.
    -                        val isWinner = winnerVoteCount != 0 && voteCount == winnerVoteCount
    -                        PollOptionViewState.PollEnded(optionId, optionAnswer, voteCount, votePercentage, isWinner)
    -                    } else if (isPollUndisclosed) {
    -                        // Poll is closed. Enable option, hide votes and mark the user's selection.
    -                        PollOptionViewState.PollUndisclosed(optionId, optionAnswer, isMyVote)
    -                    } else if (didUserVoted) {
    -                        // User voted to the poll, but poll is not ended. Enable option, show votes and mark the user's selection.
    -                        PollOptionViewState.PollVoted(optionId, optionAnswer, voteCount, votePercentage, isMyVote)
    -                    } else {
    -                        // User didn't voted yet and poll is not ended yet. Enable options, hide votes.
    -                        PollOptionViewState.PollReady(optionId, optionAnswer)
    -                    }
    -            )
    -        }
    -
    -        val question = pollContent.getBestPollCreationInfo()?.question?.getBestQuestion() ?: ""
    +        val pollState = createPollState(informationData, pollResponseSummary, pollContent)
    +        val pollCreationInfo = pollContent.getBestPollCreationInfo()
    +        val questionText = pollCreationInfo?.question?.getBestQuestion().orEmpty()
    +        val question = createPollQuestion(informationData, questionText, callback)
    +        val optionViewStates = pollCreationInfo?.answers?.mapToOptions(pollState, informationData)
    +        val totalVotesText = createTotalVotesText(pollState, pollResponseSummary)
     
             return PollItem_()
                     .attributes(attributes)
                     .eventId(informationData.eventId)
    -                .pollQuestion(
    -                        if (informationData.hasBeenEdited) {
    -                            annotateWithEdited(question, callback, informationData)
    -                        } else {
    -                            question
    -                        }.toEpoxyCharSequence()
    -                )
    -                .pollSent(isPollSent)
    +                .pollQuestion(question)
    +                .canVote(pollState.isVotable())
                     .totalVotesText(totalVotesText)
                     .optionViewStates(optionViewStates)
                     .edited(informationData.hasBeenEdited)
    @@ -312,53 +276,132 @@ class MessageItemFactory @Inject constructor(
                     .callback(callback)
         }
     
    -    private fun buildAudioMessageItem(messageContent: MessageAudioContent,
    -                                      @Suppress("UNUSED_PARAMETER")
    -                                      informationData: MessageInformationData,
    -                                      highlight: Boolean,
    -                                      attributes: AbsMessageItem.Attributes): MessageFileItem? {
    -        val fileUrl = messageContent.getFileUrl()?.let {
    -            if (informationData.sentByMe && !informationData.sendState.isSent()) {
    -                it
    -            } else {
    -                it.takeIf { it.isMxcUrl() }
    -            }
    -        } ?: ""
    -        return MessageFileItem_()
    +    private fun createPollState(
    +            informationData: MessageInformationData,
    +            pollResponseSummary: PollResponseData?,
    +            pollContent: MessagePollContent,
    +    ): PollState = when {
    +        !informationData.sendState.isSent()                                 -> Sending
    +        pollResponseSummary?.isClosed.orFalse()                             -> Ended
    +        pollContent.getBestPollCreationInfo()?.kind == PollType.UNDISCLOSED -> Undisclosed
    +        pollResponseSummary?.myVote?.isNotEmpty().orFalse()                 -> Voted(pollResponseSummary?.totalVotes ?: 0)
    +        else                                                                -> Ready
    +    }
    +
    +    private fun List.mapToOptions(
    +            pollState: PollState,
    +            informationData: MessageInformationData,
    +    ) = map { answer ->
    +        val pollResponseSummary = informationData.pollResponseAggregatedSummary
    +        val winnerVoteCount = pollResponseSummary?.winnerVoteCount
    +        val optionId = answer.id ?: ""
    +        val optionAnswer = answer.getBestAnswer() ?: ""
    +        val voteSummary = pollResponseSummary?.votes?.get(answer.id)
    +        val voteCount = voteSummary?.total ?: 0
    +        val votePercentage = voteSummary?.percentage ?: 0.0
    +        val isMyVote = pollResponseSummary?.myVote == answer.id
    +        val isWinner = winnerVoteCount != 0 && voteCount == winnerVoteCount
    +
    +        when (pollState) {
    +            Sending     -> PollSending(optionId, optionAnswer)
    +            Ready       -> PollReady(optionId, optionAnswer)
    +            is Voted    -> PollVoted(optionId, optionAnswer, voteCount, votePercentage, isMyVote)
    +            Undisclosed -> PollUndisclosed(optionId, optionAnswer, isMyVote)
    +            Ended       -> PollEnded(optionId, optionAnswer, voteCount, votePercentage, isWinner)
    +        }
    +    }
    +
    +    private fun createPollQuestion(
    +            informationData: MessageInformationData,
    +            question: String,
    +            callback: TimelineEventController.Callback?,
    +    ) = if (informationData.hasBeenEdited) {
    +        annotateWithEdited(question, callback, informationData)
    +    } else {
    +        question
    +    }.toEpoxyCharSequence()
    +
    +    private fun createTotalVotesText(
    +            pollState: PollState,
    +            pollResponseSummary: PollResponseData?,
    +    ): String {
    +        val votes = pollResponseSummary?.totalVotes ?: 0
    +        return when {
    +            pollState is Ended       -> stringProvider.getQuantityString(R.plurals.poll_total_vote_count_after_ended, votes, votes)
    +            pollState is Undisclosed -> ""
    +            pollState is Voted       -> stringProvider.getQuantityString(R.plurals.poll_total_vote_count_before_ended_and_voted, votes, votes)
    +            votes == 0               -> stringProvider.getString(R.string.poll_no_votes_cast)
    +            else                     -> stringProvider.getQuantityString(R.plurals.poll_total_vote_count_before_ended_and_not_voted, votes, votes)
    +        }
    +    }
    +
    +    private fun buildAudioMessageItem(
    +            params: TimelineItemFactoryParams,
    +            messageContent: MessageAudioContent,
    +            informationData: MessageInformationData,
    +            highlight: Boolean,
    +            attributes: AbsMessageItem.Attributes
    +    ): MessageAudioItem {
    +        val fileUrl = getAudioFileUrl(messageContent, informationData)
    +        val playbackControlButtonClickListener = createOnPlaybackButtonClickListener(messageContent, informationData, params)
    +        val duration = messageContent.audioInfo?.duration ?: 0
    +
    +        return MessageAudioItem_()
                     .attributes(attributes)
    -                .izLocalFile(localFilesHelper.isLocalFile(fileUrl))
    -                .izDownloaded(session.fileService().isFileInCache(
    -                        fileUrl,
    -                        messageContent.getFileName(),
    -                        messageContent.mimeType,
    -                        messageContent.encryptedFileInfo?.toElementToDecrypt())
    -                )
    +                .filename(messageContent.body)
    +                .duration(messageContent.audioInfo?.duration ?: 0)
    +                .playbackControlButtonClickListener(playbackControlButtonClickListener)
    +                .audioMessagePlaybackTracker(audioMessagePlaybackTracker)
    +                .isLocalFile(localFilesHelper.isLocalFile(fileUrl))
    +                .fileSize(messageContent.audioInfo?.size ?: 0L)
    +                .onSeek { params.callback?.onAudioSeekBarMovedTo(informationData.eventId, duration, it) }
                     .mxcUrl(fileUrl)
                     .contentUploadStateTrackerBinder(contentUploadStateTrackerBinder)
                     .contentDownloadStateTrackerBinder(contentDownloadStateTrackerBinder)
                     .highlighted(highlight)
                     .leftGuideline(avatarSizeProvider.leftGuideline)
    -                .filename(messageContent.body)
    -                .iconRes(R.drawable.ic_headphones)
         }
     
    -    private fun buildVoiceMessageItem(params: TimelineItemFactoryParams,
    -                                      messageContent: MessageAudioContent,
    -                                      @Suppress("UNUSED_PARAMETER")
    -                                      informationData: MessageInformationData,
    -                                      highlight: Boolean,
    -                                      attributes: AbsMessageItem.Attributes): MessageVoiceItem? {
    -        val fileUrl = messageContent.getFileUrl()?.let {
    -            if (informationData.sentByMe && !informationData.sendState.isSent()) {
    -                it
    -            } else {
    -                it.takeIf { it.isMxcUrl() }
    -            }
    -        } ?: ""
    +    private fun getAudioFileUrl(
    +            messageContent: MessageAudioContent,
    +            informationData: MessageInformationData,
    +    ) = messageContent.getFileUrl()?.let {
    +        if (informationData.sentByMe && !informationData.sendState.isSent()) {
    +            it
    +        } else {
    +            it.takeIf { it.isMxcUrl() }
    +        }
    +    } ?: ""
     
    -        val playbackControlButtonClickListener: ClickListener = object : ClickListener {
    -            override fun invoke(view: View) {
    -                params.callback?.onVoiceControlButtonClicked(informationData.eventId, messageContent)
    +    private fun createOnPlaybackButtonClickListener(
    +            messageContent: MessageAudioContent,
    +            informationData: MessageInformationData,
    +            params: TimelineItemFactoryParams,
    +    ) = object : ClickListener {
    +        override fun invoke(view: View) {
    +            params.callback?.onVoiceControlButtonClicked(informationData.eventId, messageContent)
    +        }
    +    }
    +
    +    private fun buildVoiceMessageItem(
    +            params: TimelineItemFactoryParams,
    +            messageContent: MessageAudioContent,
    +            informationData: MessageInformationData,
    +            highlight: Boolean,
    +            attributes: AbsMessageItem.Attributes
    +    ): MessageVoiceItem {
    +        val fileUrl = getAudioFileUrl(messageContent, informationData)
    +        val playbackControlButtonClickListener = createOnPlaybackButtonClickListener(messageContent, informationData, params)
    +
    +        val waveformTouchListener: MessageVoiceItem.WaveformTouchListener = object : MessageVoiceItem.WaveformTouchListener {
    +            override fun onWaveformTouchedUp(percentage: Float) {
    +                val duration = messageContent.audioInfo?.duration ?: 0
    +                params.callback?.onVoiceWaveformTouchedUp(informationData.eventId, duration, percentage)
    +            }
    +
    +            override fun onWaveformMovedTo(percentage: Float) {
    +                val duration = messageContent.audioInfo?.duration ?: 0
    +                params.callback?.onVoiceWaveformMovedTo(informationData.eventId, duration, percentage)
                 }
             }
     
    @@ -367,14 +410,9 @@ class MessageItemFactory @Inject constructor(
                     .duration(messageContent.audioWaveformInfo?.duration ?: 0)
                     .waveform(messageContent.audioWaveformInfo?.waveform?.toFft().orEmpty())
                     .playbackControlButtonClickListener(playbackControlButtonClickListener)
    -                .voiceMessagePlaybackTracker(voiceMessagePlaybackTracker)
    -                .izLocalFile(localFilesHelper.isLocalFile(fileUrl))
    -                .izDownloaded(session.fileService().isFileInCache(
    -                        fileUrl,
    -                        messageContent.getFileName(),
    -                        messageContent.mimeType,
    -                        messageContent.encryptedFileInfo?.toElementToDecrypt())
    -                )
    +                .waveformTouchListener(waveformTouchListener)
    +                .audioMessagePlaybackTracker(audioMessagePlaybackTracker)
    +                .isLocalFile(localFilesHelper.isLocalFile(fileUrl))
                     .mxcUrl(fileUrl)
                     .contentUploadStateTrackerBinder(contentUploadStateTrackerBinder)
                     .contentDownloadStateTrackerBinder(contentDownloadStateTrackerBinder)
    @@ -382,12 +420,14 @@ class MessageItemFactory @Inject constructor(
                     .leftGuideline(avatarSizeProvider.leftGuideline)
         }
     
    -    private fun buildVerificationRequestMessageItem(messageContent: MessageVerificationRequestContent,
    -                                                    @Suppress("UNUSED_PARAMETER")
    -                                                    informationData: MessageInformationData,
    -                                                    highlight: Boolean,
    -                                                    callback: TimelineEventController.Callback?,
    -                                                    attributes: AbsMessageItem.Attributes): VerificationRequestItem? {
    +    private fun buildVerificationRequestMessageItem(
    +            messageContent: MessageVerificationRequestContent,
    +            @Suppress("UNUSED_PARAMETER")
    +            informationData: MessageInformationData,
    +            highlight: Boolean,
    +            callback: TimelineEventController.Callback?,
    +            attributes: AbsMessageItem.Attributes,
    +    ): VerificationRequestItem? {
             // If this request is not sent by me or sent to me, we should ignore it in timeline
             val myUserId = session.myUserId
             if (informationData.senderId != myUserId && messageContent.toUserId != myUserId) {
    @@ -396,7 +436,7 @@ class MessageItemFactory @Inject constructor(
     
             val otherUserId = if (informationData.sentByMe) messageContent.toUserId else informationData.senderId
             val otherUserName = if (informationData.sentByMe) {
    -            session.getRoomMember(messageContent.toUserId, roomId)?.displayName
    +            session.roomService().getRoomMember(messageContent.toUserId, roomId)?.displayName
             } else {
                 informationData.memberName
             }
    @@ -414,7 +454,7 @@ class MessageItemFactory @Inject constructor(
                                     reactionPillCallback = attributes.reactionPillCallback,
                                     readReceiptsCallback = attributes.readReceiptsCallback,
                                     emojiTypeFace = attributes.emojiTypeFace,
    -                                reactionsSummaryEvents = attributes.reactionsSummaryEvents
    +                                reactionsSummaryEvents = attributes.reactionsSummaryEvents,
                             )
                     )
                     .callback(callback)
    @@ -422,17 +462,17 @@ class MessageItemFactory @Inject constructor(
                     .leftGuideline(avatarSizeProvider.leftGuideline)
         }
     
    -    private fun buildFileMessageItem(messageContent: MessageFileContent,
    -//                                     informationData: MessageInformationData,
    -                                     highlight: Boolean,
    -//                                     callback: TimelineEventController.Callback?,
    -                                     attributes: AbsMessageItem.Attributes): MessageFileItem? {
    +    private fun buildFileMessageItem(
    +            messageContent: MessageFileContent,
    +            highlight: Boolean,
    +            attributes: AbsMessageItem.Attributes,
    +    ): MessageFileItem {
             val mxcUrl = messageContent.getFileUrl() ?: ""
             return MessageFileItem_()
                     .attributes(attributes)
                     .leftGuideline(avatarSizeProvider.leftGuideline)
    -                .izLocalFile(localFilesHelper.isLocalFile(messageContent.getFileUrl()))
    -                .izDownloaded(session.fileService().isFileInCache(messageContent))
    +                .isLocalFile(localFilesHelper.isLocalFile(messageContent.getFileUrl()))
    +                .isDownloaded(session.fileService().isFileInCache(messageContent))
                     .mxcUrl(mxcUrl)
                     .contentUploadStateTrackerBinder(contentUploadStateTrackerBinder)
                     .contentDownloadStateTrackerBinder(contentDownloadStateTrackerBinder)
    @@ -441,21 +481,36 @@ class MessageItemFactory @Inject constructor(
                     .iconRes(R.drawable.ic_paperclip)
         }
     
    -    private fun buildNotHandledMessageItem(messageContent: MessageContent,
    -                                           informationData: MessageInformationData,
    -                                           highlight: Boolean,
    -                                           callback: TimelineEventController.Callback?,
    -                                           attributes: AbsMessageItem.Attributes): MessageTextItem? {
    +    private fun buildAudioContent(
    +            params: TimelineItemFactoryParams,
    +            messageContent: MessageAudioContent,
    +            informationData: MessageInformationData,
    +            highlight: Boolean,
    +            attributes: AbsMessageItem.Attributes,
    +    ) = if (messageContent.voiceMessageIndicator != null) {
    +        buildVoiceMessageItem(params, messageContent, informationData, highlight, attributes)
    +    } else {
    +        buildAudioMessageItem(params, messageContent, informationData, highlight, attributes)
    +    }
    +
    +    private fun buildNotHandledMessageItem(
    +            messageContent: MessageContent,
    +            informationData: MessageInformationData,
    +            highlight: Boolean,
    +            callback: TimelineEventController.Callback?,
    +            attributes: AbsMessageItem.Attributes): MessageTextItem? {
             // For compatibility reason we should display the body
             return buildMessageTextItem(messageContent.body, false, informationData, highlight, callback, attributes)
         }
     
    -    private fun buildImageMessageItem(messageContent: MessageImageInfoContent,
    -                                      @Suppress("UNUSED_PARAMETER")
    -                                      informationData: MessageInformationData,
    -                                      highlight: Boolean,
    -                                      callback: TimelineEventController.Callback?,
    -                                      attributes: AbsMessageItem.Attributes): MessageImageVideoItem? {
    +    private fun buildImageMessageItem(
    +            messageContent: MessageImageInfoContent,
    +            @Suppress("UNUSED_PARAMETER")
    +            informationData: MessageInformationData,
    +            highlight: Boolean,
    +            callback: TimelineEventController.Callback?,
    +            attributes: AbsMessageItem.Attributes,
    +    ): MessageImageVideoItem? {
             val (maxWidth, maxHeight) = timelineMediaSizeProvider.getMaxSize()
             val data = ImageContentRenderer.Data(
                     eventId = informationData.eventId,
    @@ -480,19 +535,24 @@ class MessageItemFactory @Inject constructor(
                     .apply {
                         if (messageContent.msgType == MessageType.MSGTYPE_STICKER_LOCAL) {
                             mode(ImageContentRenderer.Mode.STICKER)
    +                        clickListener { view ->
    +                            callback?.onImageMessageClicked(messageContent, data, view, listOf(data))
    +                        }
                         } else {
                             clickListener { view ->
    -                            callback?.onImageMessageClicked(messageContent, data, view)
    +                            callback?.onImageMessageClicked(messageContent, data, view, emptyList())
                             }
                         }
                     }
         }
     
    -    private fun buildVideoMessageItem(messageContent: MessageVideoContent,
    -                                      informationData: MessageInformationData,
    -                                      highlight: Boolean,
    -                                      callback: TimelineEventController.Callback?,
    -                                      attributes: AbsMessageItem.Attributes): MessageImageVideoItem? {
    +    private fun buildVideoMessageItem(
    +            messageContent: MessageVideoContent,
    +            informationData: MessageInformationData,
    +            highlight: Boolean,
    +            callback: TimelineEventController.Callback?,
    +            attributes: AbsMessageItem.Attributes,
    +    ): MessageImageVideoItem? {
             val (maxWidth, maxHeight) = timelineMediaSizeProvider.getMaxSize()
             val thumbnailData = ImageContentRenderer.Data(
                     eventId = informationData.eventId,
    @@ -527,11 +587,13 @@ class MessageItemFactory @Inject constructor(
                     .clickListener { view -> callback?.onVideoMessageClicked(messageContent, videoData, view.findViewById(R.id.messageThumbnailView)) }
         }
     
    -    private fun buildItemForTextContent(messageContent: MessageTextContent,
    -                                        informationData: MessageInformationData,
    -                                        highlight: Boolean,
    -                                        callback: TimelineEventController.Callback?,
    -                                        attributes: AbsMessageItem.Attributes): VectorEpoxyModel<*>? {
    +    private fun buildItemForTextContent(
    +            messageContent: MessageTextContent,
    +            informationData: MessageInformationData,
    +            highlight: Boolean,
    +            callback: TimelineEventController.Callback?,
    +            attributes: AbsMessageItem.Attributes,
    +    ): VectorEpoxyModel<*>? {
             val matrixFormattedBody = messageContent.matrixFormattedBody
             return if (matrixFormattedBody != null) {
                 buildFormattedTextItem(matrixFormattedBody, informationData, highlight, callback, attributes)
    @@ -540,22 +602,26 @@ class MessageItemFactory @Inject constructor(
             }
         }
     
    -    private fun buildFormattedTextItem(matrixFormattedBody: String,
    -                                       informationData: MessageInformationData,
    -                                       highlight: Boolean,
    -                                       callback: TimelineEventController.Callback?,
    -                                       attributes: AbsMessageItem.Attributes): MessageTextItem? {
    +    private fun buildFormattedTextItem(
    +            matrixFormattedBody: String,
    +            informationData: MessageInformationData,
    +            highlight: Boolean,
    +            callback: TimelineEventController.Callback?,
    +            attributes: AbsMessageItem.Attributes,
    +    ): MessageTextItem? {
             val compressed = htmlCompressor.compress(matrixFormattedBody)
             val renderedFormattedBody = htmlRenderer.get().render(compressed, pillsPostProcessor) as Spanned
             return buildMessageTextItem(renderedFormattedBody, true, informationData, highlight, callback, attributes)
         }
     
    -    private fun buildMessageTextItem(body: CharSequence,
    -                                     isFormatted: Boolean,
    -                                     informationData: MessageInformationData,
    -                                     highlight: Boolean,
    -                                     callback: TimelineEventController.Callback?,
    -                                     attributes: AbsMessageItem.Attributes): MessageTextItem? {
    +    private fun buildMessageTextItem(
    +            body: CharSequence,
    +            isFormatted: Boolean,
    +            informationData: MessageInformationData,
    +            highlight: Boolean,
    +            callback: TimelineEventController.Callback?,
    +            attributes: AbsMessageItem.Attributes,
    +    ): MessageTextItem? {
             val renderedBody = textRenderer.render(body)
             val bindingOptions = spanUtils.getBindingOptions(renderedBody)
             val linkifiedBody = renderedBody.linkify(callback)
    @@ -581,9 +647,11 @@ class MessageItemFactory @Inject constructor(
                     .movementMethod(createLinkMovementMethod(callback))
         }
     
    -    private fun annotateWithEdited(linkifiedBody: CharSequence,
    -                                   callback: TimelineEventController.Callback?,
    -                                   informationData: MessageInformationData): Spannable {
    +    private fun annotateWithEdited(
    +            linkifiedBody: CharSequence,
    +            callback: TimelineEventController.Callback?,
    +            informationData: MessageInformationData,
    +    ): Spannable {
             val spannable = SpannableStringBuilder()
             spannable.append(linkifiedBody)
             val editedSuffix = stringProvider.getString(R.string.edited_suffix)
    @@ -619,12 +687,14 @@ class MessageItemFactory @Inject constructor(
             return spannable
         }
     
    -    private fun buildNoticeMessageItem(messageContent: MessageNoticeContent,
    -                                       @Suppress("UNUSED_PARAMETER")
    -                                       informationData: MessageInformationData,
    -                                       highlight: Boolean,
    -                                       callback: TimelineEventController.Callback?,
    -                                       attributes: AbsMessageItem.Attributes): MessageTextItem? {
    +    private fun buildNoticeMessageItem(
    +            messageContent: MessageNoticeContent,
    +            @Suppress("UNUSED_PARAMETER")
    +            informationData: MessageInformationData,
    +            highlight: Boolean,
    +            callback: TimelineEventController.Callback?,
    +            attributes: AbsMessageItem.Attributes,
    +    ): MessageTextItem? {
             val htmlBody = messageContent.getHtmlBody()
             val formattedBody = span {
                 text = htmlBody
    @@ -647,11 +717,13 @@ class MessageItemFactory @Inject constructor(
                     .movementMethod(createLinkMovementMethod(callback))
         }
     
    -    private fun buildEmoteMessageItem(messageContent: MessageEmoteContent,
    -                                      informationData: MessageInformationData,
    -                                      highlight: Boolean,
    -                                      callback: TimelineEventController.Callback?,
    -                                      attributes: AbsMessageItem.Attributes): MessageTextItem? {
    +    private fun buildEmoteMessageItem(
    +            messageContent: MessageEmoteContent,
    +            informationData: MessageInformationData,
    +            highlight: Boolean,
    +            callback: TimelineEventController.Callback?,
    +            attributes: AbsMessageItem.Attributes,
    +    ): MessageTextItem? {
             val formattedBody = SpannableStringBuilder()
             formattedBody.append("* ${informationData.memberName} ")
             formattedBody.append(messageContent.getHtmlBody())
    @@ -683,8 +755,10 @@ class MessageItemFactory @Inject constructor(
                     ?: body
         }
     
    -    private fun buildRedactedItem(attributes: AbsMessageItem.Attributes,
    -                                  highlight: Boolean): RedactedMessageItem? {
    +    private fun buildRedactedItem(
    +            attributes: AbsMessageItem.Attributes,
    +            highlight: Boolean,
    +    ): RedactedMessageItem? {
             return RedactedMessageItem_()
                     .layout(attributes.informationData.messageLayout.layoutRes)
                     .leftGuideline(avatarSizeProvider.leftGuideline)
    @@ -696,12 +770,13 @@ class MessageItemFactory @Inject constructor(
             return this
                     ?.filterNotNull()
                     ?.map {
    -                    // Value comes from AudioRecordView.maxReportableAmp, and 1024 is the max value in the Matrix spec
    -                    it * 22760 / 1024
    +                    // Value comes from AudioWaveformView.MAX_FFT, and 1024 is the max value in the Matrix spec
    +                    it * AudioWaveformView.MAX_FFT / 1024
                     }
         }
     
         companion object {
             private const val MAX_NUMBER_OF_EMOJI_FOR_BIG_FONT = 5
    +        const val MESSAGE_LOCATION_ITEM_HEIGHT_IN_DP = 200
         }
     }
    diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/TimelineFactory.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/TimelineFactory.kt
    index 3ec1366131..bd2a3e7ebe 100644
    --- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/TimelineFactory.kt
    +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/TimelineFactory.kt
    @@ -22,6 +22,7 @@ import im.vector.app.features.home.room.detail.timeline.merged.MergedTimelines
     import kotlinx.coroutines.CoroutineScope
     import org.matrix.android.sdk.api.session.Session
     import org.matrix.android.sdk.api.session.events.model.EventType
    +import org.matrix.android.sdk.api.session.getRoom
     import org.matrix.android.sdk.api.session.room.Room
     import org.matrix.android.sdk.api.session.room.timeline.Timeline
     import javax.inject.Inject
    diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/TimelineItemFactory.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/TimelineItemFactory.kt
    index f9d2613e27..b5d620658e 100644
    --- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/TimelineItemFactory.kt
    +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/TimelineItemFactory.kt
    @@ -26,17 +26,19 @@ import org.matrix.android.sdk.api.session.room.timeline.TimelineEvent
     import timber.log.Timber
     import javax.inject.Inject
     
    -class TimelineItemFactory @Inject constructor(private val messageItemFactory: MessageItemFactory,
    -                                              private val encryptedItemFactory: EncryptedItemFactory,
    -                                              private val noticeItemFactory: NoticeItemFactory,
    -                                              private val defaultItemFactory: DefaultItemFactory,
    -                                              private val encryptionItemFactory: EncryptionItemFactory,
    -                                              private val roomCreateItemFactory: RoomCreateItemFactory,
    -                                              private val widgetItemFactory: WidgetItemFactory,
    -                                              private val verificationConclusionItemFactory: VerificationItemFactory,
    -                                              private val callItemFactory: CallItemFactory,
    -                                              private val decryptionFailureTracker: DecryptionFailureTracker,
    -                                              private val timelineEventVisibilityHelper: TimelineEventVisibilityHelper) {
    +class TimelineItemFactory @Inject constructor(
    +        private val messageItemFactory: MessageItemFactory,
    +        private val encryptedItemFactory: EncryptedItemFactory,
    +        private val noticeItemFactory: NoticeItemFactory,
    +        private val defaultItemFactory: DefaultItemFactory,
    +        private val encryptionItemFactory: EncryptionItemFactory,
    +        private val roomCreateItemFactory: RoomCreateItemFactory,
    +        private val widgetItemFactory: WidgetItemFactory,
    +        private val verificationConclusionItemFactory: VerificationItemFactory,
    +        private val callItemFactory: CallItemFactory,
    +        private val decryptionFailureTracker: DecryptionFailureTracker,
    +        private val timelineEventVisibilityHelper: TimelineEventVisibilityHelper,
    +) {
     
         /**
          * Reminder: nextEvent is older and prevEvent is newer.
    @@ -75,16 +77,17 @@ class TimelineItemFactory @Inject constructor(private val messageItemFactory: Me
                         EventType.STATE_ROOM_ALIASES,
                         EventType.STATE_SPACE_CHILD,
                         EventType.STATE_SPACE_PARENT,
    -                    EventType.STATE_ROOM_POWER_LEVELS -> {
    +                    EventType.STATE_ROOM_POWER_LEVELS   -> {
                             noticeItemFactory.create(params)
                         }
                         EventType.STATE_ROOM_WIDGET_LEGACY,
    -                    EventType.STATE_ROOM_WIDGET       -> widgetItemFactory.create(params)
    -                    EventType.STATE_ROOM_ENCRYPTION   -> encryptionItemFactory.create(params)
    +                    EventType.STATE_ROOM_WIDGET         -> widgetItemFactory.create(params)
    +                    EventType.STATE_ROOM_ENCRYPTION     -> encryptionItemFactory.create(params)
                         // State room create
    -                    EventType.STATE_ROOM_CREATE       -> roomCreateItemFactory.create(params)
    +                    EventType.STATE_ROOM_CREATE         -> roomCreateItemFactory.create(params)
    +                    in EventType.STATE_ROOM_BEACON_INFO -> messageItemFactory.create(params)
                         // Unhandled state event types
    -                    else                              -> {
    +                    else                                -> {
                             // Should only happen when shouldShowHiddenEvents() settings is ON
                             Timber.v("State event type ${event.root.type} not handled")
                             defaultItemFactory.create(params)
    diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/VerificationItemFactory.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/VerificationItemFactory.kt
    index 16cf73cbb0..dfba50d209 100644
    --- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/VerificationItemFactory.kt
    +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/VerificationItemFactory.kt
    @@ -25,13 +25,14 @@ import im.vector.app.features.home.room.detail.timeline.helper.MessageInformatio
     import im.vector.app.features.home.room.detail.timeline.helper.MessageItemAttributesFactory
     import im.vector.app.features.home.room.detail.timeline.item.StatusTileTimelineItem
     import im.vector.app.features.home.room.detail.timeline.item.StatusTileTimelineItem_
    -import org.matrix.android.sdk.api.crypto.VerificationState
     import org.matrix.android.sdk.api.session.Session
     import org.matrix.android.sdk.api.session.crypto.verification.CancelCode
    +import org.matrix.android.sdk.api.session.crypto.verification.VerificationState
     import org.matrix.android.sdk.api.session.crypto.verification.safeValueOf
     import org.matrix.android.sdk.api.session.events.model.EventType
     import org.matrix.android.sdk.api.session.events.model.RelationType
     import org.matrix.android.sdk.api.session.events.model.toModel
    +import org.matrix.android.sdk.api.session.getRoom
     import org.matrix.android.sdk.api.session.room.model.message.MessageRelationContent
     import org.matrix.android.sdk.api.session.room.model.message.MessageVerificationCancelContent
     import javax.inject.Inject
    diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/format/DisplayableEventFormatter.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/format/DisplayableEventFormatter.kt
    index b83322dc9b..3c2bdb53ab 100644
    --- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/format/DisplayableEventFormatter.kt
    +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/format/DisplayableEventFormatter.kt
    @@ -59,7 +59,7 @@ class DisplayableEventFormatter @Inject constructor(
             val senderName = timelineEvent.senderInfo.disambiguatedDisplayName
     
             return when (timelineEvent.root.getClearType()) {
    -            EventType.MESSAGE               -> {
    +            EventType.MESSAGE                   -> {
                     timelineEvent.getLastMessageContent()?.let { messageContent ->
                         when (messageContent.msgType) {
                             MessageType.MSGTYPE_TEXT                 -> {
    @@ -100,17 +100,17 @@ class DisplayableEventFormatter @Inject constructor(
                         }
                     } ?: span { }
                 }
    -            EventType.STICKER               -> {
    +            EventType.STICKER                   -> {
                     simpleFormat(senderName, stringProvider.getString(R.string.send_a_sticker), appendAuthor)
                 }
    -            EventType.REACTION              -> {
    +            EventType.REACTION                  -> {
                     timelineEvent.root.getClearContent().toModel()?.relatesTo?.let {
                         val emojiSpanned = emojiSpanify.spanify(stringProvider.getString(R.string.sent_a_reaction, it.key))
                         simpleFormat(senderName, emojiSpanned, appendAuthor)
                     } ?: span { }
                 }
                 EventType.KEY_VERIFICATION_CANCEL,
    -            EventType.KEY_VERIFICATION_DONE -> {
    +            EventType.KEY_VERIFICATION_DONE     -> {
                     // cancel and done can appear in timeline, so should have representation
                     simpleFormat(senderName, stringProvider.getString(R.string.sent_verification_conclusion), appendAuthor)
                 }
    @@ -119,20 +119,23 @@ class DisplayableEventFormatter @Inject constructor(
                 EventType.KEY_VERIFICATION_MAC,
                 EventType.KEY_VERIFICATION_KEY,
                 EventType.KEY_VERIFICATION_READY,
    -            EventType.CALL_CANDIDATES       -> {
    +            EventType.CALL_CANDIDATES           -> {
                     span { }
                 }
    -            in EventType.POLL_START         -> {
    +            in EventType.POLL_START             -> {
                     timelineEvent.root.getClearContent().toModel(catchError = true)?.getBestPollCreationInfo()?.question?.getBestQuestion()
                             ?: stringProvider.getString(R.string.sent_a_poll)
                 }
    -            in EventType.POLL_RESPONSE      -> {
    +            in EventType.POLL_RESPONSE          -> {
                     stringProvider.getString(R.string.poll_response_room_list_preview)
                 }
    -            in EventType.POLL_END           -> {
    +            in EventType.POLL_END               -> {
                     stringProvider.getString(R.string.poll_end_room_list_preview)
                 }
    -            else                            -> {
    +            in EventType.STATE_ROOM_BEACON_INFO -> {
    +                simpleFormat(senderName, stringProvider.getString(R.string.sent_live_location), appendAuthor)
    +            }
    +            else                                -> {
                     span {
                         text = noticeEventFormatter.format(timelineEvent, isDm) ?: ""
                         textStyle = "italic"
    @@ -167,7 +170,7 @@ class DisplayableEventFormatter @Inject constructor(
             }
     
             return when (event.getClearType()) {
    -            EventType.MESSAGE       -> {
    +            EventType.MESSAGE                   -> {
                     (event.getClearContent().toModel() as? MessageContent)?.let { messageContent ->
                         when (messageContent.msgType) {
                             MessageType.MSGTYPE_TEXT                 -> {
    @@ -208,25 +211,28 @@ class DisplayableEventFormatter @Inject constructor(
                         }
                     } ?: span { }
                 }
    -            EventType.STICKER       -> {
    +            EventType.STICKER                   -> {
                     stringProvider.getString(R.string.send_a_sticker)
                 }
    -            EventType.REACTION      -> {
    +            EventType.REACTION                  -> {
                     event.getClearContent().toModel()?.relatesTo?.let {
                         emojiSpanify.spanify(stringProvider.getString(R.string.sent_a_reaction, it.key))
                     } ?: span { }
                 }
    -            in EventType.POLL_START    -> {
    +            in EventType.POLL_START             -> {
                     event.getClearContent().toModel(catchError = true)?.pollCreationInfo?.question?.question
                             ?: stringProvider.getString(R.string.sent_a_poll)
                 }
    -            in EventType.POLL_RESPONSE -> {
    +            in EventType.POLL_RESPONSE          -> {
                     stringProvider.getString(R.string.poll_response_room_list_preview)
                 }
    -            in EventType.POLL_END      -> {
    +            in EventType.POLL_END               -> {
                     stringProvider.getString(R.string.poll_end_room_list_preview)
                 }
    -            else                    -> {
    +            in EventType.STATE_ROOM_BEACON_INFO -> {
    +                stringProvider.getString(R.string.sent_live_location)
    +            }
    +            else                                -> {
                     span {
                     }
                 }
    diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/format/NoticeEventFormatter.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/format/NoticeEventFormatter.kt
    index a20c1e5f97..51dc26247c 100644
    --- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/format/NoticeEventFormatter.kt
    +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/format/NoticeEventFormatter.kt
    @@ -21,10 +21,12 @@ import im.vector.app.R
     import im.vector.app.core.resources.StringProvider
     import im.vector.app.features.roomprofile.permissions.RoleFormatter
     import im.vector.app.features.settings.VectorPreferences
    +import org.matrix.android.sdk.api.crypto.MXCRYPTO_ALGORITHM_MEGOLM
     import org.matrix.android.sdk.api.extensions.appendNl
     import org.matrix.android.sdk.api.extensions.orFalse
     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.EncryptionEventContent
     import org.matrix.android.sdk.api.session.events.model.isThread
     import org.matrix.android.sdk.api.session.events.model.toModel
     import org.matrix.android.sdk.api.session.room.model.GuestAccess
    @@ -47,8 +49,6 @@ import org.matrix.android.sdk.api.session.room.model.create.RoomCreateContent
     import org.matrix.android.sdk.api.session.room.powerlevels.PowerLevelsHelper
     import org.matrix.android.sdk.api.session.room.timeline.TimelineEvent
     import org.matrix.android.sdk.api.session.widgets.model.WidgetContent
    -import org.matrix.android.sdk.internal.crypto.MXCRYPTO_ALGORITHM_MEGOLM
    -import org.matrix.android.sdk.internal.crypto.model.event.EncryptionEventContent
     import timber.log.Timber
     import javax.inject.Inject
     
    diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/helper/VoiceMessagePlaybackTracker.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/helper/AudioMessagePlaybackTracker.kt
    similarity index 73%
    rename from vector/src/main/java/im/vector/app/features/home/room/detail/timeline/helper/VoiceMessagePlaybackTracker.kt
    rename to vector/src/main/java/im/vector/app/features/home/room/detail/timeline/helper/AudioMessagePlaybackTracker.kt
    index c6204bff1c..0312ac9e6f 100644
    --- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/helper/VoiceMessagePlaybackTracker.kt
    +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/helper/AudioMessagePlaybackTracker.kt
    @@ -22,7 +22,7 @@ import javax.inject.Inject
     import javax.inject.Singleton
     
     @Singleton
    -class VoiceMessagePlaybackTracker @Inject constructor() {
    +class AudioMessagePlaybackTracker @Inject constructor() {
     
         private val mainHandler = Handler(Looper.getMainLooper())
         private val listeners = mutableMapOf()
    @@ -33,7 +33,7 @@ class VoiceMessagePlaybackTracker @Inject constructor() {
             activityListeners.add(listener)
         }
     
    -    fun unTrackActivity(listener: ActivityListener) {
    +    fun untrackActivity(listener: ActivityListener) {
             activityListeners.remove(listener)
         }
     
    @@ -46,10 +46,16 @@ class VoiceMessagePlaybackTracker @Inject constructor() {
             }
         }
     
    -    fun unTrack(id: String) {
    +    fun untrack(id: String) {
             listeners.remove(id)
         }
     
    +    fun pauseAllPlaybacks() {
    +        listeners.keys.forEach { key ->
    +            pausePlayback(key)
    +        }
    +    }
    +
         fun makeAllPlaybacksIdle() {
             listeners.keys.forEach { key ->
                 setState(key, Listener.State.Idle)
    @@ -70,7 +76,8 @@ class VoiceMessagePlaybackTracker @Inject constructor() {
     
         fun startPlayback(id: String) {
             val currentPlaybackTime = getPlaybackTime(id)
    -        val currentState = Listener.State.Playing(currentPlaybackTime)
    +        val currentPercentage = getPercentage(id)
    +        val currentState = Listener.State.Playing(currentPlaybackTime, currentPercentage)
             setState(id, currentState)
             // Pause any active playback
             states
    @@ -86,16 +93,23 @@ class VoiceMessagePlaybackTracker @Inject constructor() {
         }
     
         fun pausePlayback(id: String) {
    -        val currentPlaybackTime = getPlaybackTime(id)
    -        setState(id, Listener.State.Paused(currentPlaybackTime))
    +        if (getPlaybackState(id) is Listener.State.Playing) {
    +            val currentPlaybackTime = getPlaybackTime(id)
    +            val currentPercentage = getPercentage(id)
    +            setState(id, Listener.State.Paused(currentPlaybackTime, currentPercentage))
    +        }
         }
     
         fun stopPlayback(id: String) {
             setState(id, Listener.State.Idle)
         }
     
    -    fun updateCurrentPlaybackTime(id: String, time: Int) {
    -        setState(id, Listener.State.Playing(time))
    +    fun updatePlayingAtPlaybackTime(id: String, time: Int, percentage: Float) {
    +        setState(id, Listener.State.Playing(time, percentage))
    +    }
    +
    +    fun updatePausedAtPlaybackTime(id: String, time: Int, percentage: Float) {
    +        setState(id, Listener.State.Paused(time, percentage))
         }
     
         fun updateCurrentRecording(id: String, amplitudeList: List) {
    @@ -113,6 +127,15 @@ class VoiceMessagePlaybackTracker @Inject constructor() {
             }
         }
     
    +    private fun getPercentage(id: String): Float {
    +        return when (val state = states[id]) {
    +            is Listener.State.Playing -> state.percentage
    +            is Listener.State.Paused  -> state.percentage
    +            /* Listener.State.Idle, */
    +            else                      -> 0f
    +        }
    +    }
    +
         fun clear() {
             listeners.forEach {
                 it.value.onUpdate(Listener.State.Idle)
    @@ -131,8 +154,8 @@ class VoiceMessagePlaybackTracker @Inject constructor() {
     
             sealed class State {
                 object Idle : State()
    -            data class Playing(val playbackTime: Int) : State()
    -            data class Paused(val playbackTime: Int) : State()
    +            data class Playing(val playbackTime: Int, val percentage: Float) : State()
    +            data class Paused(val playbackTime: Int, val percentage: Float) : State()
                 data class Recording(val amplitudeList: List) : State()
             }
         }
    diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/helper/ContentUploadStateTrackerBinder.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/helper/ContentUploadStateTrackerBinder.kt
    index 0909cbe8de..9ff8ddfbce 100644
    --- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/helper/ContentUploadStateTrackerBinder.kt
    +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/helper/ContentUploadStateTrackerBinder.kt
    @@ -26,7 +26,6 @@ import dagger.hilt.android.scopes.ActivityScoped
     import im.vector.app.R
     import im.vector.app.core.di.ActiveSessionHolder
     import im.vector.app.core.error.ErrorFormatter
    -import im.vector.app.core.extensions.exhaustive
     import im.vector.app.core.utils.TextUtils
     import im.vector.app.features.home.room.detail.timeline.MessageColorProvider
     import org.matrix.android.sdk.api.session.content.ContentUploadStateTracker
    @@ -86,7 +85,7 @@ private class ContentMediaProgressUpdater(private val progressLayout: ViewGroup,
                 is ContentUploadStateTracker.State.Success             -> handleSuccess()
                 is ContentUploadStateTracker.State.CompressingImage    -> handleCompressingImage()
                 is ContentUploadStateTracker.State.CompressingVideo    -> handleCompressingVideo(state)
    -        }.exhaustive
    +        }
         }
     
         private fun handleIdle() {
    diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/helper/LocationPinProvider.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/helper/LocationPinProvider.kt
    index 7262284c95..8ef910c931 100644
    --- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/helper/LocationPinProvider.kt
    +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/helper/LocationPinProvider.kt
    @@ -29,6 +29,7 @@ import im.vector.app.core.di.ActiveSessionHolder
     import im.vector.app.core.glide.GlideApp
     import im.vector.app.core.utils.DimensionConverter
     import im.vector.app.features.home.AvatarRenderer
    +import org.matrix.android.sdk.api.session.getUser
     import org.matrix.android.sdk.api.util.toMatrixItem
     import timber.log.Timber
     import javax.inject.Inject
    diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/helper/MessageInformationDataFactory.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/helper/MessageInformationDataFactory.kt
    index 97b3a8f445..f882840eee 100644
    --- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/helper/MessageInformationDataFactory.kt
    +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/helper/MessageInformationDataFactory.kt
    @@ -27,10 +27,11 @@ import im.vector.app.features.home.room.detail.timeline.item.PollVoteSummaryData
     import im.vector.app.features.home.room.detail.timeline.item.ReferencesInfoData
     import im.vector.app.features.home.room.detail.timeline.item.SendStateDecoration
     import im.vector.app.features.home.room.detail.timeline.style.TimelineMessageLayoutFactory
    -import org.matrix.android.sdk.api.crypto.VerificationState
     import org.matrix.android.sdk.api.extensions.orFalse
     import org.matrix.android.sdk.api.session.Session
    +import org.matrix.android.sdk.api.session.crypto.verification.VerificationState
     import org.matrix.android.sdk.api.session.events.model.EventType
    +import org.matrix.android.sdk.api.session.events.model.content.EncryptedEventContent
     import org.matrix.android.sdk.api.session.events.model.isAttachmentMessage
     import org.matrix.android.sdk.api.session.events.model.toModel
     import org.matrix.android.sdk.api.session.room.model.ReferencesAggregatedContent
    @@ -40,7 +41,6 @@ import org.matrix.android.sdk.api.session.room.send.SendState
     import org.matrix.android.sdk.api.session.room.timeline.TimelineEvent
     import org.matrix.android.sdk.api.session.room.timeline.getLastMessageContent
     import org.matrix.android.sdk.api.session.room.timeline.hasBeenEdited
    -import org.matrix.android.sdk.internal.crypto.model.event.EncryptedEventContent
     import javax.inject.Inject
     
     /**
    diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/helper/TimelineDisplayableEvents.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/helper/TimelineDisplayableEvents.kt
    index 96a2ca4609..1736b20d44 100644
    --- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/helper/TimelineDisplayableEvents.kt
    +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/helper/TimelineDisplayableEvents.kt
    @@ -50,8 +50,8 @@ object TimelineDisplayableEvents {
                 EventType.STATE_ROOM_TOMBSTONE,
                 EventType.STATE_ROOM_JOIN_RULES,
                 EventType.KEY_VERIFICATION_DONE,
    -            EventType.KEY_VERIFICATION_CANCEL
    -    ) + EventType.POLL_START
    +            EventType.KEY_VERIFICATION_CANCEL,
    +    ) + EventType.POLL_START + EventType.STATE_ROOM_BEACON_INFO
     }
     
     fun TimelineEvent.canBeMerged(): Boolean {
    @@ -71,7 +71,7 @@ fun TimelineEvent.isRoomConfiguration(roomCreatorUserId: String?): Boolean {
             EventType.STATE_ROOM_CANONICAL_ALIAS,
             EventType.STATE_ROOM_POWER_LEVELS,
             EventType.STATE_ROOM_ENCRYPTION -> true
    -        EventType.STATE_ROOM_MEMBER -> {
    +        EventType.STATE_ROOM_MEMBER     -> {
                 // Keep only room member events regarding the room creator (when he joined the room),
                 // but exclude events where the room creator invite others, or where others join
                 roomCreatorUserId != null && root.stateKey == roomCreatorUserId
    diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/helper/TimelineVisibilityStateChangedListeners.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/helper/TimelineVisibilityStateChangedListeners.kt
    index 2337a6ea15..95feef83c0 100644
    --- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/helper/TimelineVisibilityStateChangedListeners.kt
    +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/helper/TimelineVisibilityStateChangedListeners.kt
    @@ -22,7 +22,7 @@ import im.vector.app.features.home.room.detail.timeline.TimelineEventController
     import org.matrix.android.sdk.api.session.room.timeline.TimelineEvent
     
     class ReadMarkerVisibilityStateChangedListener(private val callback: TimelineEventController.Callback?) :
    -    VectorEpoxyModel.OnVisibilityStateChangedListener {
    +        VectorEpoxyModel.OnVisibilityStateChangedListener {
     
         override fun onVisibilityStateChanged(visibilityState: Int) {
             if (visibilityState == VisibilityState.VISIBLE) {
    @@ -33,7 +33,7 @@ class ReadMarkerVisibilityStateChangedListener(private val callback: TimelineEve
     
     class TimelineEventVisibilityStateChangedListener(private val callback: TimelineEventController.Callback?,
                                                       private val event: TimelineEvent) :
    -    VectorEpoxyModel.OnVisibilityStateChangedListener {
    +        VectorEpoxyModel.OnVisibilityStateChangedListener {
     
         override fun onVisibilityStateChanged(visibilityState: Int) {
             if (visibilityState == VisibilityState.VISIBLE) {
    @@ -46,7 +46,7 @@ class TimelineEventVisibilityStateChangedListener(private val callback: Timeline
     
     class MergedTimelineEventVisibilityStateChangedListener(private val callback: TimelineEventController.Callback?,
                                                             private val events: List) :
    -    VectorEpoxyModel.OnVisibilityStateChangedListener {
    +        VectorEpoxyModel.OnVisibilityStateChangedListener {
     
         override fun onVisibilityStateChanged(visibilityState: Int) {
             if (visibilityState == VisibilityState.VISIBLE) {
    diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/image/ImageContentRendererFactory.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/image/ImageContentRendererFactory.kt
    index 2ad58df3b8..26cab4d863 100644
    --- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/image/ImageContentRendererFactory.kt
    +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/image/ImageContentRendererFactory.kt
    @@ -17,6 +17,7 @@
     package im.vector.app.features.home.room.detail.timeline.image
     
     import im.vector.app.features.media.ImageContentRenderer
    +import org.matrix.android.sdk.api.session.crypto.attachments.toElementToDecrypt
     import org.matrix.android.sdk.api.session.events.model.isImageMessage
     import org.matrix.android.sdk.api.session.events.model.isVideoMessage
     import org.matrix.android.sdk.api.session.events.model.toModel
    @@ -25,7 +26,6 @@ import org.matrix.android.sdk.api.session.room.model.message.MessageVideoContent
     import org.matrix.android.sdk.api.session.room.model.message.getFileUrl
     import org.matrix.android.sdk.api.session.room.model.message.getThumbnailUrl
     import org.matrix.android.sdk.api.session.room.timeline.TimelineEvent
    -import org.matrix.android.sdk.internal.crypto.attachments.toElementToDecrypt
     
     fun TimelineEvent.buildImageContentRendererData(maxHeight: Int): ImageContentRenderer.Data? {
         return when {
    diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/AbsBaseMessageItem.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/AbsBaseMessageItem.kt
    index 4f08c9d05f..c21cb5319e 100644
    --- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/AbsBaseMessageItem.kt
    +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/AbsBaseMessageItem.kt
    @@ -39,7 +39,7 @@ import im.vector.app.features.home.room.detail.timeline.TimelineEventController
     import im.vector.app.features.home.room.detail.timeline.view.TimelineMessageLayoutRenderer
     import im.vector.app.features.reactions.widget.ReactionButton
     import im.vector.app.features.themes.ThemeUtils
    -import org.matrix.android.sdk.api.crypto.RoomEncryptionTrustLevel
    +import org.matrix.android.sdk.api.session.crypto.model.RoomEncryptionTrustLevel
     import org.matrix.android.sdk.api.session.room.send.SendState
     
     private const val MAX_REACTIONS_TO_SHOW = 8
    diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/CallTileTimelineItem.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/CallTileTimelineItem.kt
    index ea130901b1..b56f5264e6 100644
    --- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/CallTileTimelineItem.kt
    +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/CallTileTimelineItem.kt
    @@ -209,13 +209,13 @@ abstract class CallTileTimelineItem : AbsBaseMessageItem {
                     holder.statusView.setStatus(R.string.call_tile_video_active)
                 }
    -            attributes.informationData.sentByMe -> {
    +            attributes.informationData.sentByMe        -> {
                     holder.statusView.setStatus(R.string.call_ringing)
                 }
    -            attributes.callKind.isVoiceCall     -> {
    +            attributes.callKind.isVoiceCall            -> {
                     holder.statusView.setStatus(R.string.call_tile_voice_incoming)
                 }
    -            else                                -> {
    +            else                                       -> {
                     holder.statusView.setStatus(R.string.call_tile_video_incoming)
                 }
             }
    diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/MergedRoomCreationItem.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/MergedRoomCreationItem.kt
    index 9f631f7a0e..f3ca525136 100644
    --- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/MergedRoomCreationItem.kt
    +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/MergedRoomCreationItem.kt
    @@ -133,7 +133,10 @@ abstract class MergedRoomCreationItem : BasedMergedItem() {
    +
    +    @EpoxyAttribute
    +    var filename: String = ""
    +
    +    @EpoxyAttribute
    +    var mxcUrl: String = ""
    +
    +    @EpoxyAttribute
    +    var duration: Int = 0
    +
    +    @EpoxyAttribute
    +    var fileSize: Long = 0
    +
    +    @EpoxyAttribute
    +    @JvmField
    +    var isLocalFile = false
    +
    +    @EpoxyAttribute(EpoxyAttribute.Option.DoNotHash)
    +    var onSeek: ((percentage: Float) -> Unit)? = null
    +
    +    @EpoxyAttribute
    +    lateinit var contentUploadStateTrackerBinder: ContentUploadStateTrackerBinder
    +
    +    @EpoxyAttribute
    +    lateinit var contentDownloadStateTrackerBinder: ContentDownloadStateTrackerBinder
    +
    +    @EpoxyAttribute(EpoxyAttribute.Option.DoNotHash)
    +    var playbackControlButtonClickListener: ClickListener? = null
    +
    +    @EpoxyAttribute
    +    lateinit var audioMessagePlaybackTracker: AudioMessagePlaybackTracker
    +
    +    private var isUserSeeking = false
    +
    +    override fun bind(holder: Holder) {
    +        super.bind(holder)
    +        renderSendState(holder.rootLayout, null)
    +        bindViewAttributes(holder)
    +        bindUploadState(holder)
    +        applyLayoutTint(holder)
    +        bindSeekBar(holder)
    +        holder.audioPlaybackControlButton.setOnClickListener { playbackControlButtonClickListener?.invoke(it) }
    +        renderStateBasedOnAudioPlayback(holder)
    +    }
    +
    +    private fun bindUploadState(holder: Holder) {
    +        if (attributes.informationData.sendState.hasFailed()) {
    +            holder.audioPlaybackControlButton.setImageResource(R.drawable.ic_cross)
    +            holder.audioPlaybackControlButton.contentDescription =
    +                    holder.view.context.getString(R.string.error_audio_message_unable_to_play, filename)
    +            holder.progressLayout.isVisible = false
    +        } else {
    +            contentUploadStateTrackerBinder.bind(attributes.informationData.eventId, isLocalFile, holder.progressLayout)
    +        }
    +    }
    +
    +    private fun applyLayoutTint(holder: Holder) {
    +        val backgroundTint = if (attributes.informationData.messageLayout is TimelineMessageLayout.Bubble) {
    +            Color.TRANSPARENT
    +        } else {
    +            ThemeUtils.getColor(holder.view.context, R.attr.vctr_content_quinary)
    +        }
    +        holder.mainLayout.backgroundTintList = ColorStateList.valueOf(backgroundTint)
    +    }
    +
    +    private fun bindViewAttributes(holder: Holder) {
    +        val formattedDuration = formatPlaybackTime(duration)
    +        val formattedFileSize = TextUtils.formatFileSize(holder.rootLayout.context, fileSize, true)
    +        val durationContentDescription = getPlaybackTimeContentDescription(holder.rootLayout.context, duration)
    +
    +        holder.filenameView.text = filename
    +        holder.filenameView.onClick(attributes.itemClickListener)
    +        holder.audioPlaybackDuration.text = formattedDuration
    +        holder.fileSize.text = holder.rootLayout.context.getString(
    +                R.string.audio_message_file_size, formattedFileSize
    +        )
    +        holder.mainLayout.contentDescription = holder.rootLayout.context.getString(
    +                R.string.a11y_audio_message_item, filename, durationContentDescription, formattedFileSize
    +        )
    +    }
    +
    +    private fun bindSeekBar(holder: Holder) {
    +        holder.audioSeekBar.setOnSeekBarChangeListener(object : SeekBar.OnSeekBarChangeListener {
    +            override fun onProgressChanged(seekBar: SeekBar, progress: Int, fromUser: Boolean) {
    +                holder.audioPlaybackTime.text = formatPlaybackTime(
    +                        (duration * (progress.toFloat() / 100)).toInt()
    +                )
    +            }
    +
    +            override fun onStartTrackingTouch(seekBar: SeekBar) {
    +                isUserSeeking = true
    +            }
    +
    +            override fun onStopTrackingTouch(seekBar: SeekBar) {
    +                isUserSeeking = false
    +                val percentage = seekBar.progress.toFloat() / 100
    +                onSeek?.invoke(percentage)
    +            }
    +        })
    +    }
    +
    +    private fun renderStateBasedOnAudioPlayback(holder: Holder) {
    +        audioMessagePlaybackTracker.track(attributes.informationData.eventId, object : AudioMessagePlaybackTracker.Listener {
    +            override fun onUpdate(state: AudioMessagePlaybackTracker.Listener.State) {
    +                when (state) {
    +                    is AudioMessagePlaybackTracker.Listener.State.Idle      -> renderIdleState(holder)
    +                    is AudioMessagePlaybackTracker.Listener.State.Playing   -> renderPlayingState(holder, state)
    +                    is AudioMessagePlaybackTracker.Listener.State.Paused    -> renderPausedState(holder, state)
    +                    is AudioMessagePlaybackTracker.Listener.State.Recording -> Unit
    +                }
    +            }
    +        })
    +    }
    +
    +    private fun renderIdleState(holder: Holder) {
    +        holder.audioPlaybackControlButton.setImageResource(R.drawable.ic_play_pause_play)
    +        holder.audioPlaybackControlButton.contentDescription =
    +                holder.view.context.getString(R.string.a11y_play_audio_message, filename)
    +        holder.audioPlaybackTime.text = formatPlaybackTime(duration)
    +        holder.audioSeekBar.progress = 0
    +    }
    +
    +    private fun renderPlayingState(holder: Holder, state: AudioMessagePlaybackTracker.Listener.State.Playing) {
    +        holder.audioPlaybackControlButton.setImageResource(R.drawable.ic_play_pause_pause)
    +        holder.audioPlaybackControlButton.contentDescription =
    +                holder.view.context.getString(R.string.a11y_pause_audio_message, filename)
    +
    +        if (!isUserSeeking) {
    +            holder.audioPlaybackTime.text = formatPlaybackTime(state.playbackTime)
    +            holder.audioSeekBar.progress = (state.percentage * 100).toInt()
    +        }
    +    }
    +
    +    private fun renderPausedState(holder: Holder, state: AudioMessagePlaybackTracker.Listener.State.Paused) {
    +        holder.audioPlaybackControlButton.setImageResource(R.drawable.ic_play_pause_play)
    +        holder.audioPlaybackControlButton.contentDescription =
    +                holder.view.context.getString(R.string.a11y_play_audio_message, filename)
    +        holder.audioPlaybackTime.text = formatPlaybackTime(state.playbackTime)
    +        holder.audioSeekBar.progress = (state.percentage * 100).toInt()
    +    }
    +
    +    private fun formatPlaybackTime(time: Int) = DateUtils.formatElapsedTime((time / 1000).toLong())
    +
    +    private fun getPlaybackTimeContentDescription(context: Context, time: Int): String {
    +        val formattedPlaybackTime = formatPlaybackTime(time)
    +        val (minutes, seconds) = formattedPlaybackTime.split(":").map { it.toIntOrNull() ?: 0 }
    +        return context.getString(R.string.a11y_audio_playback_duration, minutes, seconds)
    +    }
    +
    +    override fun unbind(holder: Holder) {
    +        super.unbind(holder)
    +        contentUploadStateTrackerBinder.unbind(attributes.informationData.eventId)
    +        contentDownloadStateTrackerBinder.unbind(mxcUrl)
    +        audioMessagePlaybackTracker.untrack(attributes.informationData.eventId)
    +    }
    +
    +    override fun getViewStubId() = STUB_ID
    +
    +    class Holder : AbsMessageItem.Holder(STUB_ID) {
    +        val rootLayout by bind(R.id.messageRootLayout)
    +        val mainLayout by bind(R.id.messageMainInnerLayout)
    +        val filenameView by bind(R.id.messageFilenameView)
    +        val audioPlaybackControlButton by bind(R.id.audioPlaybackControlButton)
    +        val audioPlaybackTime by bind(R.id.audioPlaybackTime)
    +        val progressLayout by bind(R.id.messageFileUploadProgressLayout)
    +        val fileSize by bind(R.id.fileSize)
    +        val audioPlaybackDuration by bind(R.id.audioPlaybackDuration)
    +        val audioSeekBar by bind(R.id.audioSeekBar)
    +    }
    +
    +    companion object {
    +        private const val STUB_ID = R.id.messageContentAudioStub
    +    }
    +}
    diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/MessageFileItem.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/MessageFileItem.kt
    index 8b6899daee..8a94f927f9 100644
    --- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/MessageFileItem.kt
    +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/MessageFileItem.kt
    @@ -47,14 +47,13 @@ abstract class MessageFileItem : AbsMessageItem() {
         @DrawableRes
         var iconRes: Int = 0
     
    -//    @EpoxyAttribute(EpoxyAttribute.Option.DoNotHash)
    -//    var clickListener: ClickListener? = null
    +    @EpoxyAttribute
    +    @JvmField
    +    var isLocalFile = false
     
         @EpoxyAttribute
    -    var izLocalFile = false
    -
    -    @EpoxyAttribute
    -    var izDownloaded = false
    +    @JvmField
    +    var isDownloaded = false
     
         @EpoxyAttribute
         lateinit var contentUploadStateTrackerBinder: ContentUploadStateTrackerBinder
    @@ -65,17 +64,20 @@ abstract class MessageFileItem : AbsMessageItem() {
         override fun bind(holder: Holder) {
             super.bind(holder)
             renderSendState(holder.fileLayout, holder.filenameView)
    +
             if (!attributes.informationData.sendState.hasFailed()) {
    -            contentUploadStateTrackerBinder.bind(attributes.informationData.eventId, izLocalFile, holder.progressLayout)
    +            contentUploadStateTrackerBinder.bind(attributes.informationData.eventId, isLocalFile, holder.progressLayout)
             } else {
                 holder.fileImageView.setImageResource(R.drawable.ic_cross)
                 holder.progressLayout.isVisible = false
             }
    +
             holder.filenameView.text = filename
    +
             if (attributes.informationData.sendState.isSending()) {
                 holder.fileImageView.setImageResource(iconRes)
             } else {
    -            if (izDownloaded) {
    +            if (isDownloaded) {
                     holder.fileImageView.setImageResource(iconRes)
                     holder.fileDownloadProgress.progress = 0
                 } else {
    @@ -83,7 +85,7 @@ abstract class MessageFileItem : AbsMessageItem() {
                     holder.fileImageView.setImageResource(R.drawable.ic_download)
                 }
             }
    -//        holder.view.setOnClickListener(clickListener)
    +
             val backgroundTint = if (attributes.informationData.messageLayout is TimelineMessageLayout.Bubble) {
                 Color.TRANSPARENT
             } else {
    diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/MessageInformationData.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/MessageInformationData.kt
    index 04b558b14f..9620077fd8 100644
    --- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/MessageInformationData.kt
    +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/MessageInformationData.kt
    @@ -19,7 +19,7 @@ package im.vector.app.features.home.room.detail.timeline.item
     import android.os.Parcelable
     import im.vector.app.features.home.room.detail.timeline.style.TimelineMessageLayout
     import kotlinx.parcelize.Parcelize
    -import org.matrix.android.sdk.api.crypto.VerificationState
    +import org.matrix.android.sdk.api.session.crypto.verification.VerificationState
     import org.matrix.android.sdk.api.session.room.send.SendState
     import org.matrix.android.sdk.api.util.MatrixItem
     
    diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/MessageLiveLocationStartItem.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/MessageLiveLocationStartItem.kt
    new file mode 100644
    index 0000000000..390db0ef50
    --- /dev/null
    +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/MessageLiveLocationStartItem.kt
    @@ -0,0 +1,95 @@
    +/*
    + * Copyright (c) 2022 New Vector Ltd
    + *
    + * Licensed under the Apache License, Version 2.0 (the "License");
    + * you may not use this file except in compliance with the License.
    + * You may obtain a copy of the License at
    + *
    + *     http://www.apache.org/licenses/LICENSE-2.0
    + *
    + * Unless required by applicable law or agreed to in writing, software
    + * distributed under the License is distributed on an "AS IS" BASIS,
    + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    + * See the License for the specific language governing permissions and
    + * limitations under the License.
    + */
    +
    +package im.vector.app.features.home.room.detail.timeline.item
    +
    +import android.graphics.drawable.ColorDrawable
    +import android.widget.ImageView
    +import androidx.core.view.updateLayoutParams
    +import com.airbnb.epoxy.EpoxyAttribute
    +import com.airbnb.epoxy.EpoxyModelClass
    +import com.bumptech.glide.load.resource.bitmap.GranularRoundedCorners
    +import com.bumptech.glide.load.resource.bitmap.RoundedCorners
    +import im.vector.app.R
    +import im.vector.app.core.glide.GlideApp
    +import im.vector.app.core.utils.DimensionConverter
    +import im.vector.app.features.home.room.detail.timeline.style.TimelineMessageLayout
    +import im.vector.app.features.home.room.detail.timeline.style.granularRoundedCorners
    +import im.vector.app.features.themes.ThemeUtils
    +
    +@EpoxyModelClass(layout = R.layout.item_timeline_event_base)
    +abstract class MessageLiveLocationStartItem : AbsMessageItem() {
    +
    +    @EpoxyAttribute
    +    var mapWidth: Int = 0
    +
    +    @EpoxyAttribute
    +    var mapHeight: Int = 0
    +
    +    override fun bind(holder: Holder) {
    +        super.bind(holder)
    +        renderSendState(holder.view, null)
    +        bindMap(holder)
    +        bindBottomBanner(holder)
    +    }
    +
    +    private fun bindMap(holder: Holder) {
    +        val messageLayout = attributes.informationData.messageLayout
    +        val mapCornerTransformation = if (messageLayout is TimelineMessageLayout.Bubble) {
    +            messageLayout.cornersRadius.granularRoundedCorners()
    +        } else {
    +            RoundedCorners(getDefaultLayoutCornerRadiusInDp(holder))
    +        }
    +        holder.noLocationMapImageView.updateLayoutParams {
    +            width = mapWidth
    +            height = mapHeight
    +        }
    +        GlideApp.with(holder.noLocationMapImageView)
    +                .load(R.drawable.bg_no_location_map)
    +                .transform(mapCornerTransformation)
    +                .into(holder.noLocationMapImageView)
    +    }
    +
    +    private fun bindBottomBanner(holder: Holder) {
    +        val messageLayout = attributes.informationData.messageLayout
    +        val imageCornerTransformation = if (messageLayout is TimelineMessageLayout.Bubble) {
    +            GranularRoundedCorners(0f, 0f, messageLayout.cornersRadius.bottomEndRadius, messageLayout.cornersRadius.bottomStartRadius)
    +        } else {
    +            val bottomCornerRadius = getDefaultLayoutCornerRadiusInDp(holder).toFloat()
    +            GranularRoundedCorners(0f, 0f, bottomCornerRadius, bottomCornerRadius)
    +        }
    +        GlideApp.with(holder.bannerImageView)
    +                .load(ColorDrawable(ThemeUtils.getColor(holder.bannerImageView.context, R.attr.colorSurface)))
    +                .transform(imageCornerTransformation)
    +                .into(holder.bannerImageView)
    +    }
    +
    +    private fun getDefaultLayoutCornerRadiusInDp(holder: Holder): Int {
    +        val dimensionConverter = DimensionConverter(holder.view.resources)
    +        return dimensionConverter.dpToPx(8)
    +    }
    +
    +    override fun getViewStubId() = STUB_ID
    +
    +    class Holder : AbsMessageItem.Holder(STUB_ID) {
    +        val bannerImageView by bind(R.id.locationLiveStartBanner)
    +        val noLocationMapImageView by bind(R.id.locationLiveStartMap)
    +    }
    +
    +    companion object {
    +        private const val STUB_ID = R.id.messageContentLiveLocationStartStub
    +    }
    +}
    diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/MessageVoiceItem.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/MessageVoiceItem.kt
    index e9f728d976..82860da886 100644
    --- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/MessageVoiceItem.kt
    +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/MessageVoiceItem.kt
    @@ -19,25 +19,32 @@ package im.vector.app.features.home.room.detail.timeline.item
     import android.content.res.ColorStateList
     import android.graphics.Color
     import android.text.format.DateUtils
    +import android.view.MotionEvent
     import android.view.View
     import android.view.ViewGroup
     import android.widget.ImageButton
     import android.widget.TextView
    +import androidx.core.view.doOnLayout
     import androidx.core.view.isVisible
     import com.airbnb.epoxy.EpoxyAttribute
     import com.airbnb.epoxy.EpoxyModelClass
    -import com.visualizer.amplitude.AudioRecordView
     import im.vector.app.R
     import im.vector.app.core.epoxy.ClickListener
    +import im.vector.app.features.home.room.detail.timeline.helper.AudioMessagePlaybackTracker
     import im.vector.app.features.home.room.detail.timeline.helper.ContentDownloadStateTrackerBinder
     import im.vector.app.features.home.room.detail.timeline.helper.ContentUploadStateTrackerBinder
    -import im.vector.app.features.home.room.detail.timeline.helper.VoiceMessagePlaybackTracker
     import im.vector.app.features.home.room.detail.timeline.style.TimelineMessageLayout
     import im.vector.app.features.themes.ThemeUtils
    +import im.vector.app.features.voice.AudioWaveformView
     
     @EpoxyModelClass(layout = R.layout.item_timeline_event_base)
     abstract class MessageVoiceItem : AbsMessageItem() {
     
    +    interface WaveformTouchListener {
    +        fun onWaveformTouchedUp(percentage: Float)
    +        fun onWaveformMovedTo(percentage: Float)
    +    }
    +
         @EpoxyAttribute
         var mxcUrl: String = ""
     
    @@ -48,10 +55,8 @@ abstract class MessageVoiceItem : AbsMessageItem() {
         var waveform: List = emptyList()
     
         @EpoxyAttribute
    -    var izLocalFile = false
    -
    -    @EpoxyAttribute
    -    var izDownloaded = false
    +    @JvmField
    +    var isLocalFile = false
     
         @EpoxyAttribute
         lateinit var contentUploadStateTrackerBinder: ContentUploadStateTrackerBinder
    @@ -62,27 +67,25 @@ abstract class MessageVoiceItem : AbsMessageItem() {
         @EpoxyAttribute(EpoxyAttribute.Option.DoNotHash)
         var playbackControlButtonClickListener: ClickListener? = null
     
    +    @EpoxyAttribute(EpoxyAttribute.Option.DoNotHash)
    +    var waveformTouchListener: WaveformTouchListener? = null
    +
         @EpoxyAttribute
    -    lateinit var voiceMessagePlaybackTracker: VoiceMessagePlaybackTracker
    +    lateinit var audioMessagePlaybackTracker: AudioMessagePlaybackTracker
     
         override fun bind(holder: Holder) {
             super.bind(holder)
             renderSendState(holder.voiceLayout, null)
             if (!attributes.informationData.sendState.hasFailed()) {
    -            contentUploadStateTrackerBinder.bind(attributes.informationData.eventId, izLocalFile, holder.progressLayout)
    +            contentUploadStateTrackerBinder.bind(attributes.informationData.eventId, isLocalFile, holder.progressLayout)
             } else {
                 holder.voicePlaybackControlButton.setImageResource(R.drawable.ic_cross)
                 holder.voicePlaybackControlButton.contentDescription = holder.view.context.getString(R.string.error_voice_message_unable_to_play)
                 holder.progressLayout.isVisible = false
             }
     
    -        holder.voicePlaybackWaveform.setOnLongClickListener(attributes.itemLongClickListener)
    -
    -        holder.voicePlaybackWaveform.post {
    -            holder.voicePlaybackWaveform.recreate()
    -            waveform.forEach { amplitude ->
    -                holder.voicePlaybackWaveform.update(amplitude)
    -            }
    +        holder.voicePlaybackWaveform.doOnLayout {
    +            onWaveformViewReady(holder)
             }
     
             val backgroundTint = if (attributes.informationData.messageLayout is TimelineMessageLayout.Bubble) {
    @@ -91,35 +94,68 @@ abstract class MessageVoiceItem : AbsMessageItem() {
                 ThemeUtils.getColor(holder.view.context, R.attr.vctr_content_quinary)
             }
             holder.voicePlaybackLayout.backgroundTintList = ColorStateList.valueOf(backgroundTint)
    +    }
    +
    +    private fun onWaveformViewReady(holder: Holder) {
    +        holder.voicePlaybackWaveform.setOnLongClickListener(attributes.itemLongClickListener)
             holder.voicePlaybackControlButton.setOnClickListener { playbackControlButtonClickListener?.invoke(it) }
     
    -        voiceMessagePlaybackTracker.track(attributes.informationData.eventId, object : VoiceMessagePlaybackTracker.Listener {
    -            override fun onUpdate(state: VoiceMessagePlaybackTracker.Listener.State) {
    +        val waveformColorIdle = ThemeUtils.getColor(holder.view.context, R.attr.vctr_content_quaternary)
    +        val waveformColorPlayed = ThemeUtils.getColor(holder.view.context, R.attr.vctr_content_secondary)
    +
    +        holder.voicePlaybackWaveform.clear()
    +        waveform.forEach { amplitude ->
    +            holder.voicePlaybackWaveform.add(AudioWaveformView.FFT(amplitude.toFloat(), waveformColorIdle))
    +        }
    +        holder.voicePlaybackWaveform.summarize()
    +
    +        holder.voicePlaybackWaveform.setOnTouchListener { view, motionEvent ->
    +            when (motionEvent.action) {
    +                MotionEvent.ACTION_UP   -> {
    +                    val percentage = getTouchedPositionPercentage(motionEvent, view)
    +                    waveformTouchListener?.onWaveformTouchedUp(percentage)
    +                }
    +                MotionEvent.ACTION_MOVE -> {
    +                    val percentage = getTouchedPositionPercentage(motionEvent, view)
    +                    waveformTouchListener?.onWaveformMovedTo(percentage)
    +                }
    +            }
    +            true
    +        }
    +
    +        audioMessagePlaybackTracker.track(attributes.informationData.eventId, object : AudioMessagePlaybackTracker.Listener {
    +            override fun onUpdate(state: AudioMessagePlaybackTracker.Listener.State) {
                     when (state) {
    -                    is VoiceMessagePlaybackTracker.Listener.State.Idle    -> renderIdleState(holder)
    -                    is VoiceMessagePlaybackTracker.Listener.State.Playing -> renderPlayingState(holder, state)
    -                    is VoiceMessagePlaybackTracker.Listener.State.Paused  -> renderPausedState(holder, state)
    +                    is AudioMessagePlaybackTracker.Listener.State.Idle      -> renderIdleState(holder, waveformColorIdle, waveformColorPlayed)
    +                    is AudioMessagePlaybackTracker.Listener.State.Playing   -> renderPlayingState(holder, state, waveformColorIdle, waveformColorPlayed)
    +                    is AudioMessagePlaybackTracker.Listener.State.Paused    -> renderPausedState(holder, state, waveformColorIdle, waveformColorPlayed)
    +                    is AudioMessagePlaybackTracker.Listener.State.Recording -> Unit
                     }
                 }
             })
         }
     
    -    private fun renderIdleState(holder: Holder) {
    +    private fun getTouchedPositionPercentage(motionEvent: MotionEvent, view: View) = (motionEvent.x / view.width).coerceIn(0f, 1f)
    +
    +    private fun renderIdleState(holder: Holder, idleColor: Int, playedColor: Int) {
             holder.voicePlaybackControlButton.setImageResource(R.drawable.ic_play_pause_play)
             holder.voicePlaybackControlButton.contentDescription = holder.view.context.getString(R.string.a11y_play_voice_message)
             holder.voicePlaybackTime.text = formatPlaybackTime(duration)
    +        holder.voicePlaybackWaveform.updateColors(0f, playedColor, idleColor)
         }
     
    -    private fun renderPlayingState(holder: Holder, state: VoiceMessagePlaybackTracker.Listener.State.Playing) {
    +    private fun renderPlayingState(holder: Holder, state: AudioMessagePlaybackTracker.Listener.State.Playing, idleColor: Int, playedColor: Int) {
             holder.voicePlaybackControlButton.setImageResource(R.drawable.ic_play_pause_pause)
             holder.voicePlaybackControlButton.contentDescription = holder.view.context.getString(R.string.a11y_pause_voice_message)
             holder.voicePlaybackTime.text = formatPlaybackTime(state.playbackTime)
    +        holder.voicePlaybackWaveform.updateColors(state.percentage, playedColor, idleColor)
         }
     
    -    private fun renderPausedState(holder: Holder, state: VoiceMessagePlaybackTracker.Listener.State.Paused) {
    +    private fun renderPausedState(holder: Holder, state: AudioMessagePlaybackTracker.Listener.State.Paused, idleColor: Int, playedColor: Int) {
             holder.voicePlaybackControlButton.setImageResource(R.drawable.ic_play_pause_play)
             holder.voicePlaybackControlButton.contentDescription = holder.view.context.getString(R.string.a11y_play_voice_message)
             holder.voicePlaybackTime.text = formatPlaybackTime(state.playbackTime)
    +        holder.voicePlaybackWaveform.updateColors(state.percentage, playedColor, idleColor)
         }
     
         private fun formatPlaybackTime(time: Int) = DateUtils.formatElapsedTime((time / 1000).toLong())
    @@ -128,7 +164,7 @@ abstract class MessageVoiceItem : AbsMessageItem() {
             super.unbind(holder)
             contentUploadStateTrackerBinder.unbind(attributes.informationData.eventId)
             contentDownloadStateTrackerBinder.unbind(mxcUrl)
    -        voiceMessagePlaybackTracker.unTrack(attributes.informationData.eventId)
    +        audioMessagePlaybackTracker.untrack(attributes.informationData.eventId)
         }
     
         override fun getViewStubId() = STUB_ID
    @@ -138,7 +174,7 @@ abstract class MessageVoiceItem : AbsMessageItem() {
             val voiceLayout by bind(R.id.voiceLayout)
             val voicePlaybackControlButton by bind(R.id.voicePlaybackControlButton)
             val voicePlaybackTime by bind(R.id.voicePlaybackTime)
    -        val voicePlaybackWaveform by bind(R.id.voicePlaybackWaveform)
    +        val voicePlaybackWaveform by bind(R.id.voicePlaybackWaveform)
             val progressLayout by bind(R.id.messageFileUploadProgressLayout)
         }
     
    diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/NoticeItem.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/NoticeItem.kt
    index 3c3510a073..7ca5166542 100644
    --- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/NoticeItem.kt
    +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/NoticeItem.kt
    @@ -28,7 +28,7 @@ import im.vector.app.core.ui.views.ShieldImageView
     import im.vector.app.features.home.AvatarRenderer
     import im.vector.app.features.home.room.detail.timeline.TimelineEventController
     import im.vector.lib.core.utils.epoxy.charsequence.EpoxyCharSequence
    -import org.matrix.android.sdk.api.crypto.RoomEncryptionTrustLevel
    +import org.matrix.android.sdk.api.session.crypto.model.RoomEncryptionTrustLevel
     
     @EpoxyModelClass(layout = R.layout.item_timeline_event_base_noinfo)
     abstract class NoticeItem : BaseEventItem() {
    diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/PollItem.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/PollItem.kt
    index 2327a0f2e2..273dd0299a 100644
    --- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/PollItem.kt
    +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/PollItem.kt
    @@ -39,13 +39,13 @@ abstract class PollItem : AbsMessageItem() {
         var eventId: String? = null
     
         @EpoxyAttribute
    -    var pollSent: Boolean = false
    +    var canVote: Boolean = false
     
         @EpoxyAttribute
         var totalVotesText: String? = null
     
    -   @EpoxyAttribute
    -   var edited: Boolean = false
    +    @EpoxyAttribute
    +    var edited: Boolean = false
     
         @EpoxyAttribute
         lateinit var optionViewStates: List
    @@ -54,7 +54,6 @@ abstract class PollItem : AbsMessageItem() {
     
         override fun bind(holder: Holder) {
             super.bind(holder)
    -        val relatedEventId = eventId ?: return
     
             renderSendState(holder.view, holder.questionTextView)
     
    @@ -73,13 +72,19 @@ abstract class PollItem : AbsMessageItem() {
             optionViewStates.forEachIndexed { index, optionViewState ->
                 views.getOrNull(index)?.let {
                     it.render(optionViewState)
    -                it.setOnClickListener {
    -                    callback?.onTimelineItemAction(RoomDetailAction.VoteToPoll(relatedEventId, optionViewState.optionId))
    -                }
    +                it.setOnClickListener { onPollItemClick(optionViewState) }
                 }
             }
         }
     
    +    private fun onPollItemClick(optionViewState: PollOptionViewState) {
    +        val relatedEventId = eventId
    +
    +        if (canVote && relatedEventId != null) {
    +            callback?.onTimelineItemAction(RoomDetailAction.VoteToPoll(relatedEventId, optionViewState.optionId))
    +        }
    +    }
    +
         class Holder : AbsMessageItem.Holder(STUB_ID) {
             val questionTextView by bind(R.id.questionTextView)
             val optionsContainer by bind(R.id.optionsContainer)
    diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/PollOptionView.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/PollOptionView.kt
    index 2be933d9c3..80daa595b6 100644
    --- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/PollOptionView.kt
    +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/PollOptionView.kt
    @@ -23,7 +23,6 @@ import androidx.appcompat.content.res.AppCompatResources
     import androidx.constraintlayout.widget.ConstraintLayout
     import androidx.core.view.isVisible
     import im.vector.app.R
    -import im.vector.app.core.extensions.exhaustive
     import im.vector.app.core.extensions.setAttributeTintedImageResource
     import im.vector.app.databinding.ItemPollOptionBinding
     
    @@ -49,7 +48,7 @@ class PollOptionView @JvmOverloads constructor(
                 is PollOptionViewState.PollReady       -> renderPollReady()
                 is PollOptionViewState.PollVoted       -> renderPollVoted(state)
                 is PollOptionViewState.PollUndisclosed -> renderPollUndisclosed(state)
    -        }.exhaustive
    +        }
         }
     
         private fun renderPollSending() {
    diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/StatusTileTimelineItem.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/StatusTileTimelineItem.kt
    index 2d9119f14c..3810f1cb6f 100644
    --- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/StatusTileTimelineItem.kt
    +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/StatusTileTimelineItem.kt
    @@ -57,7 +57,7 @@ abstract class StatusTileTimelineItem : AbsBaseMessageItem R.drawable.ic_shield_trusted
                 ShieldUIState.BLACK -> R.drawable.ic_shield_black
                 ShieldUIState.RED   -> R.drawable.ic_shield_warning
    -            ShieldUIState.ERROR   -> R.drawable.ic_warning_badge
    +            ShieldUIState.ERROR -> R.drawable.ic_warning_badge
             }
     
             holder.titleView.setCompoundDrawablesWithIntrinsicBounds(
    diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/VerificationRequestItem.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/VerificationRequestItem.kt
    index 821531416b..0e6530fdca 100644
    --- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/VerificationRequestItem.kt
    +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/VerificationRequestItem.kt
    @@ -31,13 +31,12 @@ import com.airbnb.epoxy.EpoxyModelClass
     import im.vector.app.R
     import im.vector.app.core.epoxy.ClickListener
     import im.vector.app.core.epoxy.onClick
    -import im.vector.app.core.extensions.exhaustive
     import im.vector.app.features.home.AvatarRenderer
     import im.vector.app.features.home.room.detail.RoomDetailAction
     import im.vector.app.features.home.room.detail.timeline.MessageColorProvider
     import im.vector.app.features.home.room.detail.timeline.TimelineEventController
    -import org.matrix.android.sdk.api.crypto.VerificationState
     import org.matrix.android.sdk.api.session.crypto.verification.VerificationService
    +import org.matrix.android.sdk.api.session.crypto.verification.VerificationState
     
     @EpoxyModelClass(layout = R.layout.item_timeline_event_base_state)
     abstract class VerificationRequestItem : AbsBaseMessageItem() {
    @@ -105,7 +104,7 @@ abstract class VerificationRequestItem : AbsBaseMessageItem {
    +            Uninitialized,
    +            is Loading -> {
                     genericLoaderItem {
                         id("Spinner")
                     }
                 }
    -            is Fail       -> {
    +            is Fail    -> {
                     genericFooterItem {
                         id("failure")
                         text(host.stringProvider.getString(R.string.unknown_error).toEpoxyCharSequence())
                     }
                 }
    -            is Success    -> {
    +            is Success -> {
                     state.mapReactionKeyToMemberList()?.forEach { reactionInfo ->
                         reactionInfoSimpleItem {
                             id(reactionInfo.eventId)
    diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/reactions/ViewReactionsViewModel.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/reactions/ViewReactionsViewModel.kt
    index 25d6f907b5..5f4befd035 100644
    --- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/reactions/ViewReactionsViewModel.kt
    +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/reactions/ViewReactionsViewModel.kt
    @@ -33,6 +33,7 @@ import im.vector.app.core.platform.VectorViewModel
     import im.vector.app.features.home.room.detail.timeline.action.TimelineEventFragmentArgs
     import kotlinx.coroutines.flow.map
     import org.matrix.android.sdk.api.session.Session
    +import org.matrix.android.sdk.api.session.getRoom
     import org.matrix.android.sdk.flow.flow
     import org.matrix.android.sdk.flow.unwrap
     
    @@ -56,10 +57,10 @@ data class ReactionInfo(
     /**
      * Used to display the list of members that reacted to a given event
      */
    -class ViewReactionsViewModel @AssistedInject constructor(@Assisted
    -                                                         initialState: DisplayReactionsViewState,
    -                                                         session: Session,
    -                                                         private val dateFormatter: VectorDateFormatter
    +class ViewReactionsViewModel @AssistedInject constructor(
    +        @Assisted initialState: DisplayReactionsViewState,
    +        session: Session,
    +        private val dateFormatter: VectorDateFormatter
     ) : VectorViewModel(initialState) {
     
         private val roomId = initialState.roomId
    diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/render/EventTextRenderer.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/render/EventTextRenderer.kt
    index d50a6fb297..83cffc4279 100644
    --- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/render/EventTextRenderer.kt
    +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/render/EventTextRenderer.kt
    @@ -62,7 +62,7 @@ class EventTextRenderer @AssistedInject constructor(@Assisted private val roomId
          * ========================================================================================== */
     
         private fun addNotifyEveryoneSpans(text: Spannable, roomId: String) {
    -        val room: RoomSummary? = sessionHolder.getSafeActiveSession()?.getRoomSummary(roomId)
    +        val room: RoomSummary? = sessionHolder.getSafeActiveSession()?.roomService()?.getRoomSummary(roomId)
             val matrixItem = MatrixItem.EveryoneInRoomItem(
                     id = roomId,
                     avatarUrl = room?.avatarUrl,
    diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/style/TimelineMessageLayout.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/style/TimelineMessageLayout.kt
    index c0e668e013..a41732be2a 100644
    --- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/style/TimelineMessageLayout.kt
    +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/style/TimelineMessageLayout.kt
    @@ -29,36 +29,37 @@ sealed interface TimelineMessageLayout : Parcelable {
     
         @Parcelize
         data class Default(
    -        override val showAvatar: Boolean,
    -        override val showDisplayName: Boolean,
    -        override val showTimestamp: Boolean,
    -        // Keep defaultLayout generated on epoxy items
    -        override val layoutRes: Int = 0,
    +            override val showAvatar: Boolean,
    +            override val showDisplayName: Boolean,
    +            override val showTimestamp: Boolean,
    +            // Keep defaultLayout generated on epoxy items
    +            override val layoutRes: Int = 0,
         ) : TimelineMessageLayout
     
         @Parcelize
         data class Bubble(
    -        override val showAvatar: Boolean,
    -        override val showDisplayName: Boolean,
    -        override val showTimestamp: Boolean = true,
    -        val addTopMargin: Boolean = false,
    -        val isIncoming: Boolean,
    -        val isPseudoBubble: Boolean,
    -        val cornersRadius: CornersRadius,
    -        val timestampAsOverlay: Boolean,
    -        override val layoutRes: Int = if (isIncoming) {
    -            R.layout.item_timeline_event_bubble_incoming_base
    -        } else {
    -            R.layout.item_timeline_event_bubble_outgoing_base
    -        },
    +            override val showAvatar: Boolean,
    +            override val showDisplayName: Boolean,
    +            override val showTimestamp: Boolean = true,
    +            val addTopMargin: Boolean = false,
    +            val isIncoming: Boolean,
    +            val isPseudoBubble: Boolean,
    +            val cornersRadius: CornersRadius,
    +            val timestampInsideMessage: Boolean,
    +            val addMessageOverlay: Boolean,
    +            override val layoutRes: Int = if (isIncoming) {
    +                R.layout.item_timeline_event_bubble_incoming_base
    +            } else {
    +                R.layout.item_timeline_event_bubble_outgoing_base
    +            },
         ) : TimelineMessageLayout {
     
             @Parcelize
             data class CornersRadius(
    -            val topStartRadius: Float,
    -            val topEndRadius: Float,
    -            val bottomStartRadius: Float,
    -            val bottomEndRadius: Float,
    +                val topStartRadius: Float,
    +                val topEndRadius: Float,
    +                val bottomStartRadius: Float,
    +                val bottomEndRadius: Float,
             ) : Parcelable
         }
     }
    diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/style/TimelineMessageLayoutFactory.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/style/TimelineMessageLayoutFactory.kt
    index 3e3e9775f8..f2334e5a4f 100644
    --- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/style/TimelineMessageLayoutFactory.kt
    +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/style/TimelineMessageLayoutFactory.kt
    @@ -31,6 +31,7 @@ import org.matrix.android.sdk.api.session.room.model.message.MessageVerification
     import org.matrix.android.sdk.api.session.room.timeline.TimelineEvent
     import org.matrix.android.sdk.api.session.room.timeline.getLastMessageContent
     import org.matrix.android.sdk.api.session.room.timeline.isEdition
    +import org.matrix.android.sdk.api.session.room.timeline.isRootThread
     import javax.inject.Inject
     
     class TimelineMessageLayoutFactory @Inject constructor(private val session: Session,
    @@ -45,7 +46,7 @@ class TimelineMessageLayoutFactory @Inject constructor(private val session: Sess
                     EventType.MESSAGE,
                     EventType.ENCRYPTED,
                     EventType.STICKER
    -        ) + EventType.POLL_START
    +        ) + EventType.POLL_START + EventType.STATE_ROOM_BEACON_INFO
     
             // Can't be rendered in bubbles, so get back to default layout
             private val MSG_TYPES_WITHOUT_BUBBLE_LAYOUT = setOf(
    @@ -57,10 +58,13 @@ class TimelineMessageLayoutFactory @Inject constructor(private val session: Sess
                     MessageType.MSGTYPE_IMAGE,
                     MessageType.MSGTYPE_VIDEO,
                     MessageType.MSGTYPE_STICKER_LOCAL,
    -                MessageType.MSGTYPE_EMOTE
    +                MessageType.MSGTYPE_EMOTE,
    +                MessageType.MSGTYPE_LIVE_LOCATION_STATE,
             )
    -        private val MSG_TYPES_WITH_TIMESTAMP_AS_OVERLAY = setOf(
    -                MessageType.MSGTYPE_IMAGE, MessageType.MSGTYPE_VIDEO
    +        private val MSG_TYPES_WITH_TIMESTAMP_INSIDE_MESSAGE = setOf(
    +                MessageType.MSGTYPE_IMAGE,
    +                MessageType.MSGTYPE_VIDEO,
    +                MessageType.MSGTYPE_LIVE_LOCATION_STATE,
             )
         }
     
    @@ -69,7 +73,7 @@ class TimelineMessageLayoutFactory @Inject constructor(private val session: Sess
         }
     
         private val isRTL: Boolean by lazy {
    -       localeProvider.isRTL()
    +        localeProvider.isRTL()
         }
     
         fun create(params: TimelineItemFactoryParams): TimelineMessageLayout {
    @@ -91,6 +95,7 @@ class TimelineMessageLayoutFactory @Inject constructor(private val session: Sess
                     nextDisplayableEvent.root.getClearType() !in listOf(EventType.MESSAGE, EventType.STICKER, EventType.ENCRYPTED) ||
                     isNextMessageReceivedMoreThanOneHourAgo ||
                     isTileTypeMessage(nextDisplayableEvent) ||
    +                event.isRootThread() ||
                     nextDisplayableEvent.isEdition()
     
             val messageLayout = when (layoutSettingsProvider.getLayoutSettings()) {
    @@ -121,7 +126,8 @@ class TimelineMessageLayoutFactory @Inject constructor(private val session: Sess
                                 isIncoming = !isSentByMe,
                                 cornersRadius = cornersRadius,
                                 isPseudoBubble = messageContent.isPseudoBubble(),
    -                            timestampAsOverlay = messageContent.timestampAsOverlay()
    +                            timestampInsideMessage = messageContent.timestampInsideMessage(),
    +                            addMessageOverlay = messageContent.shouldAddMessageOverlay(),
                         )
                     } else {
                         buildModernLayout(showInformation)
    @@ -137,10 +143,18 @@ class TimelineMessageLayoutFactory @Inject constructor(private val session: Sess
             return this.msgType in MSG_TYPES_WITH_PSEUDO_BUBBLE_LAYOUT
         }
     
    -    private fun MessageContent?.timestampAsOverlay(): Boolean {
    +    private fun MessageContent?.timestampInsideMessage(): Boolean {
             if (this == null) return false
             if (msgType == MessageType.MSGTYPE_LOCATION) return vectorPreferences.labsRenderLocationsInTimeline()
    -        return this.msgType in MSG_TYPES_WITH_TIMESTAMP_AS_OVERLAY
    +        return this.msgType in MSG_TYPES_WITH_TIMESTAMP_INSIDE_MESSAGE
    +    }
    +
    +    private fun MessageContent?.shouldAddMessageOverlay(): Boolean {
    +        return when {
    +            this == null || msgType == MessageType.MSGTYPE_LIVE_LOCATION_STATE -> false
    +            msgType == MessageType.MSGTYPE_LOCATION                            -> vectorPreferences.labsRenderLocationsInTimeline()
    +            else                                                               -> msgType in MSG_TYPES_WITH_TIMESTAMP_INSIDE_MESSAGE
    +        }
         }
     
         private fun TimelineEvent.shouldBuildBubbleLayout(): Boolean {
    diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/view/MessageBubbleView.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/view/MessageBubbleView.kt
    index 954aa0bf34..87ed9243a5 100644
    --- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/view/MessageBubbleView.kt
    +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/view/MessageBubbleView.kt
    @@ -43,9 +43,9 @@ import im.vector.app.features.themes.ThemeUtils
     import timber.log.Timber
     
     class MessageBubbleView @JvmOverloads constructor(
    -    context: Context,
    -    attrs: AttributeSet? = null,
    -    defStyleAttr: Int = 0,
    +        context: Context,
    +        attrs: AttributeSet? = null,
    +        defStyleAttr: Int = 0,
     ) : RelativeLayout(context, attrs, defStyleAttr), TimelineMessageLayoutRenderer {
     
         private var isIncoming: Boolean = false
    @@ -89,21 +89,21 @@ class MessageBubbleView @JvmOverloads constructor(
                 outlineProvider = ViewOutlineProvider.BACKGROUND
                 clipToOutline = true
                 background = RippleDrawable(
    -                ContextCompat.getColorStateList(context, R.color.mtrl_btn_ripple_color) ?: ColorStateList.valueOf(Color.TRANSPARENT),
    -                bubbleDrawable,
    -                rippleMaskDrawable)
    +                    ContextCompat.getColorStateList(context, R.color.mtrl_btn_ripple_color) ?: ColorStateList.valueOf(Color.TRANSPARENT),
    +                    bubbleDrawable,
    +                    rippleMaskDrawable)
             }
         }
     
         override fun renderMessageLayout(messageLayout: TimelineMessageLayout) {
             (messageLayout as? TimelineMessageLayout.Bubble)
    -            ?.updateDrawables()
    -            ?.setConstraintsAndColor()
    -            ?.toggleMessageOverlay()
    -            ?.setPadding()
    -            ?.setMargins()
    -            ?.setAdditionalTopSpace()
    -            ?: Timber.v("Can't render messageLayout $messageLayout")
    +                ?.updateDrawables()
    +                ?.setConstraints()
    +                ?.toggleMessageOverlay()
    +                ?.setPadding()
    +                ?.setMargins()
    +                ?.setAdditionalTopSpace()
    +                ?: Timber.v("Can't render messageLayout $messageLayout")
         }
     
         private fun TimelineMessageLayout.Bubble.updateDrawables() = apply {
    @@ -121,17 +121,13 @@ class MessageBubbleView @JvmOverloads constructor(
             rippleMaskDrawable.shapeAppearanceModel = shapeAppearanceModel
         }
     
    -    private fun TimelineMessageLayout.Bubble.setConstraintsAndColor() = apply {
    +    private fun TimelineMessageLayout.Bubble.setConstraints() = apply {
             ConstraintSet().apply {
                 clone(views.bubbleView)
                 clear(R.id.viewStubContainer, ConstraintSet.END)
    -            if (timestampAsOverlay) {
    -                val timeColor = ContextCompat.getColor(context, R.color.palette_white)
    -                views.messageTimeView.setTextColor(timeColor)
    +            if (timestampInsideMessage) {
                     connect(R.id.viewStubContainer, ConstraintSet.END, R.id.parent, ConstraintSet.END, 0)
                 } else {
    -                val timeColor = ThemeUtils.getColor(context, R.attr.vctr_content_tertiary)
    -                views.messageTimeView.setTextColor(timeColor)
                     connect(R.id.viewStubContainer, ConstraintSet.END, R.id.messageTimeView, ConstraintSet.START, 0)
                 }
                 applyTo(views.bubbleView)
    @@ -139,16 +135,20 @@ class MessageBubbleView @JvmOverloads constructor(
         }
     
         private fun TimelineMessageLayout.Bubble.toggleMessageOverlay() = apply {
    -        if (timestampAsOverlay) {
    +        if (addMessageOverlay) {
    +            val timeColor = ContextCompat.getColor(context, R.color.palette_white)
    +            views.messageTimeView.setTextColor(timeColor)
                 views.messageOverlayView.isVisible = true
                 (views.messageOverlayView.background as? GradientDrawable)?.cornerRadii = cornersRadius.toFloatArray()
             } else {
    +            val timeColor = ThemeUtils.getColor(context, R.attr.vctr_content_tertiary)
    +            views.messageTimeView.setTextColor(timeColor)
                 views.messageOverlayView.isVisible = false
             }
         }
     
         private fun TimelineMessageLayout.Bubble.setPadding() = apply {
    -        if (isPseudoBubble && timestampAsOverlay) {
    +        if (isPseudoBubble && timestampInsideMessage) {
                 views.viewStubContainer.root.setPadding(0, 0, 0, 0)
             } else {
                 views.viewStubContainer.root.setPadding(horizontalStubPadding, verticalStubPadding, horizontalStubPadding, verticalStubPadding)
    diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/upgrade/MigrateRoomViewModel.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/upgrade/MigrateRoomViewModel.kt
    index 98be65c167..6643ae4ac6 100644
    --- a/vector/src/main/java/im/vector/app/features/home/room/detail/upgrade/MigrateRoomViewModel.kt
    +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/upgrade/MigrateRoomViewModel.kt
    @@ -29,12 +29,14 @@ import im.vector.app.core.platform.VectorViewModel
     import im.vector.app.features.session.coroutineScope
     import kotlinx.coroutines.launch
     import org.matrix.android.sdk.api.session.Session
    +import org.matrix.android.sdk.api.session.getRoom
    +import org.matrix.android.sdk.api.session.getRoomSummary
     
     class MigrateRoomViewModel @AssistedInject constructor(
             @Assisted initialState: MigrateRoomViewState,
             private val session: Session,
             private val upgradeRoomViewModelTask: UpgradeRoomViewModelTask) :
    -    VectorViewModel(initialState) {
    +        VectorViewModel(initialState) {
     
         init {
             val room = session.getRoom(initialState.roomId)
    diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/upgrade/UpgradeRoomViewModelTask.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/upgrade/UpgradeRoomViewModelTask.kt
    index 8859aaeacf..7e7a3cb7cd 100644
    --- a/vector/src/main/java/im/vector/app/features/home/room/detail/upgrade/UpgradeRoomViewModelTask.kt
    +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/upgrade/UpgradeRoomViewModelTask.kt
    @@ -20,6 +20,7 @@ import im.vector.app.core.platform.ViewModelTask
     import im.vector.app.core.resources.StringProvider
     import org.matrix.android.sdk.api.extensions.tryOrNull
     import org.matrix.android.sdk.api.session.Session
    +import org.matrix.android.sdk.api.session.getRoom
     import timber.log.Timber
     import javax.inject.Inject
     
    diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/widget/RoomWidgetsController.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/widget/RoomWidgetsController.kt
    index b2da3bfc78..87392c5d7a 100644
    --- a/vector/src/main/java/im/vector/app/features/home/room/detail/widget/RoomWidgetsController.kt
    +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/widget/RoomWidgetsController.kt
    @@ -32,7 +32,7 @@ import javax.inject.Inject
     class RoomWidgetsController @Inject constructor(
             val stringProvider: StringProvider,
             val colorProvider: ColorProvider) :
    -    TypedEpoxyController>() {
    +        TypedEpoxyController>() {
     
         var listener: Listener? = null
     
    diff --git a/vector/src/main/java/im/vector/app/features/home/room/list/CollapsableTypedEpoxyController.kt b/vector/src/main/java/im/vector/app/features/home/room/list/CollapsableTypedEpoxyController.kt
    index 15b3602766..8cf7e6bcab 100644
    --- a/vector/src/main/java/im/vector/app/features/home/room/list/CollapsableTypedEpoxyController.kt
    +++ b/vector/src/main/java/im/vector/app/features/home/room/list/CollapsableTypedEpoxyController.kt
    @@ -19,7 +19,7 @@ package im.vector.app.features.home.room.list
     import com.airbnb.epoxy.EpoxyController
     
     abstract class CollapsableTypedEpoxyController :
    -    EpoxyController(), CollapsableControllerExtension {
    +        EpoxyController(), CollapsableControllerExtension {
     
         private var currentData: T? = null
         private var allowModelBuildRequests = false
    diff --git a/vector/src/main/java/im/vector/app/features/home/room/list/RoomListFragment.kt b/vector/src/main/java/im/vector/app/features/home/room/list/RoomListFragment.kt
    index 4265eebe62..aaa469f68d 100644
    --- a/vector/src/main/java/im/vector/app/features/home/room/list/RoomListFragment.kt
    +++ b/vector/src/main/java/im/vector/app/features/home/room/list/RoomListFragment.kt
    @@ -38,7 +38,6 @@ import com.google.android.material.dialog.MaterialAlertDialogBuilder
     import im.vector.app.R
     import im.vector.app.core.epoxy.LayoutManagerStateRestorer
     import im.vector.app.core.extensions.cleanup
    -import im.vector.app.core.extensions.exhaustive
     import im.vector.app.core.platform.OnBackPressed
     import im.vector.app.core.platform.StateView
     import im.vector.app.core.platform.VectorBaseFragment
    @@ -52,7 +51,7 @@ import im.vector.app.features.home.room.list.actions.RoomListQuickActionsSharedA
     import im.vector.app.features.home.room.list.actions.RoomListQuickActionsSharedActionViewModel
     import im.vector.app.features.home.room.list.widget.NotifsFabMenuView
     import im.vector.app.features.notifications.NotificationDrawerManager
    -import kotlinx.coroutines.flow.collect
    +import kotlinx.coroutines.flow.filter
     import kotlinx.coroutines.flow.launchIn
     import kotlinx.coroutines.flow.onEach
     import kotlinx.coroutines.launch
    @@ -128,7 +127,7 @@ class RoomListFragment @Inject constructor(
                     is RoomListViewEvents.SelectRoom                -> handleSelectRoom(it, it.isInviteAlreadyAccepted)
                     is RoomListViewEvents.Done                      -> Unit
                     is RoomListViewEvents.NavigateToMxToBottomSheet -> handleShowMxToLink(it.link)
    -            }.exhaustive
    +            }
             }
     
             views.createChatFabMenu.listener = this
    @@ -148,8 +147,10 @@ class RoomListFragment @Inject constructor(
         }
     
         private fun refreshCollapseStates() {
    +        val sectionsCount = adapterInfosList.count { !it.sectionHeaderAdapter.roomsSectionData.isHidden }
             roomListViewModel.sections.forEachIndexed { index, roomsSection ->
                 val actualBlock = adapterInfosList[index]
    +            val isRoomSectionCollapsable = sectionsCount > 1
                 val isRoomSectionExpanded = roomsSection.isExpanded.value.orTrue()
                 if (actualBlock.section.isExpanded && !isRoomSectionExpanded) {
                     // mark controller as collapsed
    @@ -158,12 +159,18 @@ class RoomListFragment @Inject constructor(
                     // we must expand!
                     actualBlock.contentEpoxyController.setCollapsed(false)
                 }
    -            actualBlock.section = actualBlock.section.copy(
    -                    isExpanded = isRoomSectionExpanded
    -            )
    -            actualBlock.sectionHeaderAdapter.updateSection(
    -                    actualBlock.sectionHeaderAdapter.roomsSectionData.copy(isExpanded = isRoomSectionExpanded)
    -            )
    +            actualBlock.section = actualBlock.section.copy(isExpanded = isRoomSectionExpanded)
    +            actualBlock.sectionHeaderAdapter.updateSection {
    +                it.copy(
    +                        isExpanded = isRoomSectionExpanded,
    +                        isCollapsable = isRoomSectionCollapsable
    +                )
    +            }
    +
    +            if (!isRoomSectionExpanded && !isRoomSectionCollapsable) {
    +                // force expand if the section is not collapsable
    +                roomListViewModel.handle(RoomListAction.ToggleSection(roomsSection))
    +            }
             }
         }
     
    @@ -271,13 +278,12 @@ class RoomListFragment @Inject constructor(
     
             val concatAdapter = ConcatAdapter()
     
    -        roomListViewModel.sections.forEach { section ->
    -            val sectionAdapter = SectionHeaderAdapter {
    -                roomListViewModel.handle(RoomListAction.ToggleSection(section))
    -            }.also {
    -                it.updateSection(SectionHeaderAdapter.RoomsSectionData(section.sectionName))
    +        roomListViewModel.sections.forEachIndexed { index, section ->
    +            val sectionAdapter = SectionHeaderAdapter(SectionHeaderAdapter.RoomsSectionData(section.sectionName)) {
    +                if (adapterInfosList[index].sectionHeaderAdapter.roomsSectionData.isCollapsable) {
    +                    roomListViewModel.handle(RoomListAction.ToggleSection(section))
    +                }
                 }
    -
                 val contentAdapter =
                         when {
                             section.livePages != null     -> {
    @@ -285,18 +291,23 @@ class RoomListFragment @Inject constructor(
                                         .also { controller ->
                                             section.livePages.observe(viewLifecycleOwner) { pl ->
                                                 controller.submitList(pl)
    -                                            sectionAdapter.updateSection(sectionAdapter.roomsSectionData.copy(
    -                                                    isHidden = pl.isEmpty(),
    -                                                    isLoading = false
    -                                            ))
    +                                            sectionAdapter.updateSection {
    +                                                it.copy(
    +                                                        isHidden = pl.isEmpty(),
    +                                                        isLoading = false
    +                                                )
    +                                            }
    +                                            refreshCollapseStates()
                                                 checkEmptyState()
                                             }
                                             observeItemCount(section, sectionAdapter)
                                             section.notificationCount.observe(viewLifecycleOwner) { counts ->
    -                                            sectionAdapter.updateSection(sectionAdapter.roomsSectionData.copy(
    -                                                    notificationCount = counts.totalCount,
    -                                                    isHighlighted = counts.isHighlight
    -                                            ))
    +                                            sectionAdapter.updateSection {
    +                                                it.copy(
    +                                                        notificationCount = counts.totalCount,
    +                                                        isHighlighted = counts.isHighlight,
    +                                                )
    +                                            }
                                             }
                                             section.isExpanded.observe(viewLifecycleOwner) { _ ->
                                                 refreshCollapseStates()
    @@ -309,10 +320,13 @@ class RoomListFragment @Inject constructor(
                                         .also { controller ->
                                             section.liveSuggested.observe(viewLifecycleOwner) { info ->
                                                 controller.setData(info)
    -                                            sectionAdapter.updateSection(sectionAdapter.roomsSectionData.copy(
    -                                                    isHidden = info.rooms.isEmpty(),
    -                                                    isLoading = false
    -                                            ))
    +                                            sectionAdapter.updateSection {
    +                                                it.copy(
    +                                                        isHidden = info.rooms.isEmpty(),
    +                                                        isLoading = false
    +                                                )
    +                                            }
    +                                            refreshCollapseStates()
                                                 checkEmptyState()
                                             }
                                             observeItemCount(section, sectionAdapter)
    @@ -327,17 +341,23 @@ class RoomListFragment @Inject constructor(
                                         .also { controller ->
                                             section.liveList?.observe(viewLifecycleOwner) { list ->
                                                 controller.setData(list)
    -                                            sectionAdapter.updateSection(sectionAdapter.roomsSectionData.copy(
    -                                                    isHidden = list.isEmpty(),
    -                                                    isLoading = false))
    +                                            sectionAdapter.updateSection {
    +                                                it.copy(
    +                                                        isHidden = list.isEmpty(),
    +                                                        isLoading = false,
    +                                                )
    +                                            }
    +                                            refreshCollapseStates()
                                                 checkEmptyState()
                                             }
                                             observeItemCount(section, sectionAdapter)
                                             section.notificationCount.observe(viewLifecycleOwner) { counts ->
    -                                            sectionAdapter.updateSection(sectionAdapter.roomsSectionData.copy(
    -                                                    notificationCount = counts.totalCount,
    -                                                    isHighlighted = counts.isHighlight
    -                                            ))
    +                                            sectionAdapter.updateSection {
    +                                                it.copy(
    +                                                        notificationCount = counts.totalCount,
    +                                                        isHighlighted = counts.isHighlight
    +                                                )
    +                                            }
                                             }
                                             section.isExpanded.observe(viewLifecycleOwner) { _ ->
                                                 refreshCollapseStates()
    @@ -384,10 +404,11 @@ class RoomListFragment @Inject constructor(
             lifecycleScope.launch {
                 section.itemCount
                         .flowWithLifecycle(lifecycle, Lifecycle.State.STARTED)
    +                    .filter { it > 0 }
                         .collect { count ->
    -                        sectionAdapter.updateSection(
    -                                sectionAdapter.roomsSectionData.copy(itemCount = count)
    -                        )
    +                        sectionAdapter.updateSection {
    +                            it.copy(itemCount = count)
    +                        }
                         }
             }
         }
    @@ -418,7 +439,7 @@ class RoomListFragment @Inject constructor(
                 is RoomListQuickActionsSharedAction.Leave                     -> {
                     promptLeaveRoom(quickAction.roomId)
                 }
    -        }.exhaustive
    +        }
         }
     
         private fun promptLeaveRoom(roomId: String) {
    diff --git a/vector/src/main/java/im/vector/app/features/home/room/list/RoomListSectionBuilderGroup.kt b/vector/src/main/java/im/vector/app/features/home/room/list/RoomListSectionBuilderGroup.kt
    index ec7915ba34..0a31987ae5 100644
    --- a/vector/src/main/java/im/vector/app/features/home/room/list/RoomListSectionBuilderGroup.kt
    +++ b/vector/src/main/java/im/vector/app/features/home/room/list/RoomListSectionBuilderGroup.kt
    @@ -70,22 +70,20 @@ class RoomListSectionBuilderGroup(
                             },
                             { qpm ->
                                 val name = stringProvider.getString(R.string.bottom_action_rooms)
    -                            session.getFilteredPagedRoomSummariesLive(qpm)
    -                                    .let { updatableFilterLivePageResult ->
    -                                        onUpdatable(updatableFilterLivePageResult)
    +                            val updatableFilterLivePageResult = session.roomService().getFilteredPagedRoomSummariesLive(qpm)
    +                            onUpdatable(updatableFilterLivePageResult)
     
    -                                        val itemCountFlow = updatableFilterLivePageResult.livePagedList.asFlow()
    -                                                .flatMapLatest { session.getRoomCountFlow(updatableFilterLivePageResult.queryParams) }
    -                                                .distinctUntilChanged()
    +                            val itemCountFlow = updatableFilterLivePageResult.livePagedList.asFlow()
    +                                    .flatMapLatest { session.roomService().getRoomCountLive(updatableFilterLivePageResult.queryParams).asFlow() }
    +                                    .distinctUntilChanged()
     
    -                                        sections.add(
    -                                                RoomsSection(
    -                                                        sectionName = name,
    -                                                        livePages = updatableFilterLivePageResult.livePagedList,
    -                                                        itemCount = itemCountFlow
    -                                                )
    -                                        )
    -                                    }
    +                            sections.add(
    +                                    RoomsSection(
    +                                            sectionName = name,
    +                                            livePages = updatableFilterLivePageResult.livePagedList,
    +                                            itemCount = itemCountFlow
    +                                    )
    +                            )
                             }
                     )
                 }
    @@ -252,37 +250,33 @@ class RoomListSectionBuilderGroup(
                                @StringRes nameRes: Int,
                                notifyOfLocalEcho: Boolean = false,
                                query: (RoomSummaryQueryParams.Builder) -> Unit) {
    -        withQueryParams(
    -                { query.invoke(it) },
    -                { roomQueryParams ->
    -                    val name = stringProvider.getString(nameRes)
    -                    session.getFilteredPagedRoomSummariesLive(roomQueryParams)
    -                            .also {
    -                                activeSpaceUpdaters.add(it)
    -                            }.livePagedList
    -                            .let { livePagedList ->
    -                                // use it also as a source to update count
    -                                livePagedList.asFlow()
    -                                        .onEach {
    -                                            sections.find { it.sectionName == name }
    -                                                    ?.notificationCount
    -                                                    ?.postValue(session.getNotificationCountForRooms(roomQueryParams))
    -                                        }
    -                                        .flowOn(Dispatchers.Default)
    -                                        .launchIn(coroutineScope)
    +        withQueryParams(query) { roomQueryParams ->
    +            val name = stringProvider.getString(nameRes)
    +            session.roomService().getFilteredPagedRoomSummariesLive(roomQueryParams)
    +                    .also {
    +                        activeSpaceUpdaters.add(it)
    +                    }.livePagedList
    +                    .let { livePagedList ->
    +                        // use it also as a source to update count
    +                        livePagedList.asFlow()
    +                                .onEach {
    +                                    sections.find { it.sectionName == name }
    +                                            ?.notificationCount
    +                                            ?.postValue(session.roomService().getNotificationCountForRooms(roomQueryParams))
    +                                }
    +                                .flowOn(Dispatchers.Default)
    +                                .launchIn(coroutineScope)
     
    -                                sections.add(
    -                                        RoomsSection(
    -                                                sectionName = name,
    -                                                livePages = livePagedList,
    -                                                notifyOfLocalEcho = notifyOfLocalEcho,
    -                                                itemCount = session.getRoomCountFlow(roomQueryParams)
    -                                        )
    +                        sections.add(
    +                                RoomsSection(
    +                                        sectionName = name,
    +                                        livePages = livePagedList,
    +                                        notifyOfLocalEcho = notifyOfLocalEcho,
    +                                        itemCount = session.roomService().getRoomCountLive(roomQueryParams).asFlow()
                                     )
    -                            }
    -                }
    -
    -        )
    +                        )
    +                    }
    +        }
         }
     
         private fun withQueryParams(builder: (RoomSummaryQueryParams.Builder) -> Unit, block: (RoomSummaryQueryParams) -> Unit) {
    diff --git a/vector/src/main/java/im/vector/app/features/home/room/list/RoomListSectionBuilderSpace.kt b/vector/src/main/java/im/vector/app/features/home/room/list/RoomListSectionBuilderSpace.kt
    index f82dbd43e1..59137ec490 100644
    --- a/vector/src/main/java/im/vector/app/features/home/room/list/RoomListSectionBuilderSpace.kt
    +++ b/vector/src/main/java/im/vector/app/features/home/room/list/RoomListSectionBuilderSpace.kt
    @@ -32,6 +32,7 @@ import im.vector.app.features.invite.showInvites
     import im.vector.app.space
     import kotlinx.coroutines.CoroutineScope
     import kotlinx.coroutines.Dispatchers
    +import kotlinx.coroutines.flow.MutableStateFlow
     import kotlinx.coroutines.flow.combine
     import kotlinx.coroutines.flow.distinctUntilChanged
     import kotlinx.coroutines.flow.flatMapLatest
    @@ -40,11 +41,13 @@ import kotlinx.coroutines.flow.flowOn
     import kotlinx.coroutines.flow.launchIn
     import kotlinx.coroutines.flow.map
     import kotlinx.coroutines.flow.onEach
    +import kotlinx.coroutines.flow.update
     import org.matrix.android.sdk.api.extensions.tryOrNull
     import org.matrix.android.sdk.api.query.ActiveSpaceFilter
     import org.matrix.android.sdk.api.query.RoomCategoryFilter
     import org.matrix.android.sdk.api.query.RoomTagQueryFilter
     import org.matrix.android.sdk.api.session.Session
    +import org.matrix.android.sdk.api.session.getRoomSummary
     import org.matrix.android.sdk.api.session.room.RoomSummaryQueryParams
     import org.matrix.android.sdk.api.session.room.UpdatableLivePageResult
     import org.matrix.android.sdk.api.session.room.model.Membership
    @@ -83,64 +86,10 @@ class RoomListSectionBuilderSpace(
                 }
                 RoomListDisplayMode.FILTERED      -> {
                     // Used when searching for rooms
    -                withQueryParams(
    -                        {
    -                            it.memberships = Membership.activeMemberships()
    -                        },
    -                        { qpm ->
    -                            val name = stringProvider.getString(R.string.bottom_action_rooms)
    -                            session.getFilteredPagedRoomSummariesLive(qpm)
    -                                    .let { updatableFilterLivePageResult ->
    -                                        onUpdatable(updatableFilterLivePageResult)
    -
    -                                        val itemCountFlow = updatableFilterLivePageResult.livePagedList.asFlow()
    -                                                .flatMapLatest { session.getRoomCountFlow(updatableFilterLivePageResult.queryParams) }
    -                                                .distinctUntilChanged()
    -
    -                                        sections.add(
    -                                                RoomsSection(
    -                                                        sectionName = name,
    -                                                        livePages = updatableFilterLivePageResult.livePagedList,
    -                                                        itemCount = itemCountFlow
    -                                                )
    -                                        )
    -                                    }
    -                        }
    -                )
    +                buildFilteredSection(sections)
                 }
                 RoomListDisplayMode.NOTIFICATIONS -> {
    -                if (autoAcceptInvites.showInvites()) {
    -                    addSection(
    -                            sections = sections,
    -                            activeSpaceUpdaters = activeSpaceAwareQueries,
    -                            nameRes = R.string.invitations_header,
    -                            notifyOfLocalEcho = true,
    -                            spaceFilterStrategy = if (onlyOrphansInHome) {
    -                                RoomListViewModel.SpaceFilterStrategy.ORPHANS_IF_SPACE_NULL
    -                            } else {
    -                                RoomListViewModel.SpaceFilterStrategy.ALL_IF_SPACE_NULL
    -                            },
    -                            countRoomAsNotif = true
    -                    ) {
    -                        it.memberships = listOf(Membership.INVITE)
    -                        it.roomCategoryFilter = RoomCategoryFilter.ALL
    -                    }
    -                }
    -
    -                addSection(
    -                        sections = sections,
    -                        activeSpaceUpdaters = activeSpaceAwareQueries,
    -                        nameRes = R.string.bottom_action_rooms,
    -                        notifyOfLocalEcho = false,
    -                        spaceFilterStrategy = if (onlyOrphansInHome) {
    -                            RoomListViewModel.SpaceFilterStrategy.ORPHANS_IF_SPACE_NULL
    -                        } else {
    -                            RoomListViewModel.SpaceFilterStrategy.ALL_IF_SPACE_NULL
    -                        }
    -                ) {
    -                    it.memberships = listOf(Membership.JOIN)
    -                    it.roomCategoryFilter = RoomCategoryFilter.ONLY_WITH_NOTIFICATIONS
    -                }
    +                buildNotificationsSection(sections, activeSpaceAwareQueries)
                 }
             }
     
    @@ -332,6 +281,68 @@ class RoomListSectionBuilderSpace(
             }
         }
     
    +    private fun buildNotificationsSection(sections: MutableList,
    +                                          activeSpaceAwareQueries: MutableList) {
    +        if (autoAcceptInvites.showInvites()) {
    +            addSection(
    +                    sections = sections,
    +                    activeSpaceUpdaters = activeSpaceAwareQueries,
    +                    nameRes = R.string.invitations_header,
    +                    notifyOfLocalEcho = true,
    +                    spaceFilterStrategy = if (onlyOrphansInHome) {
    +                        RoomListViewModel.SpaceFilterStrategy.ORPHANS_IF_SPACE_NULL
    +                    } else {
    +                        RoomListViewModel.SpaceFilterStrategy.ALL_IF_SPACE_NULL
    +                    },
    +                    countRoomAsNotif = true
    +            ) {
    +                it.memberships = listOf(Membership.INVITE)
    +                it.roomCategoryFilter = RoomCategoryFilter.ALL
    +            }
    +        }
    +
    +        addSection(
    +                sections = sections,
    +                activeSpaceUpdaters = activeSpaceAwareQueries,
    +                nameRes = R.string.bottom_action_rooms,
    +                notifyOfLocalEcho = false,
    +                spaceFilterStrategy = if (onlyOrphansInHome) {
    +                    RoomListViewModel.SpaceFilterStrategy.ORPHANS_IF_SPACE_NULL
    +                } else {
    +                    RoomListViewModel.SpaceFilterStrategy.ALL_IF_SPACE_NULL
    +                }
    +        ) {
    +            it.memberships = listOf(Membership.JOIN)
    +            it.roomCategoryFilter = RoomCategoryFilter.ONLY_WITH_NOTIFICATIONS
    +        }
    +    }
    +
    +    private fun buildFilteredSection(sections: MutableList) {
    +        // Used when searching for rooms
    +        withQueryParams(
    +                {
    +                    it.memberships = Membership.activeMemberships()
    +                },
    +                { qpm ->
    +                    val name = stringProvider.getString(R.string.bottom_action_rooms)
    +                    val updatableFilterLivePageResult = session.roomService().getFilteredPagedRoomSummariesLive(qpm)
    +                    onUpdatable(updatableFilterLivePageResult)
    +
    +                    val itemCountFlow = updatableFilterLivePageResult.livePagedList.asFlow()
    +                            .flatMapLatest { session.roomService().getRoomCountLive(updatableFilterLivePageResult.queryParams).asFlow() }
    +                            .distinctUntilChanged()
    +
    +                    sections.add(
    +                            RoomsSection(
    +                                    sectionName = name,
    +                                    livePages = updatableFilterLivePageResult.livePagedList,
    +                                    itemCount = itemCountFlow
    +                            )
    +                    )
    +                }
    +        )
    +    }
    +
         private fun addSection(sections: MutableList,
                                activeSpaceUpdaters: MutableList,
                                @StringRes nameRes: Int,
    @@ -339,83 +350,82 @@ class RoomListSectionBuilderSpace(
                                spaceFilterStrategy: RoomListViewModel.SpaceFilterStrategy = RoomListViewModel.SpaceFilterStrategy.NONE,
                                countRoomAsNotif: Boolean = false,
                                query: (RoomSummaryQueryParams.Builder) -> Unit) {
    -        withQueryParams(
    -                { query.invoke(it) },
    -                { roomQueryParams ->
    -                    val name = stringProvider.getString(nameRes)
    -                    session.getFilteredPagedRoomSummariesLive(
    -                            roomQueryParams.process(spaceFilterStrategy, appStateHandler.safeActiveSpaceId()),
    -                            pagedListConfig
    -                    ).also {
    -                        when (spaceFilterStrategy) {
    -                            RoomListViewModel.SpaceFilterStrategy.ORPHANS_IF_SPACE_NULL -> {
    -                                activeSpaceUpdaters.add(object : RoomListViewModel.ActiveSpaceQueryUpdater {
    -                                    override fun updateForSpaceId(roomId: String?) {
    -                                        it.queryParams = roomQueryParams.copy(
    -                                                activeSpaceFilter = ActiveSpaceFilter.ActiveSpace(roomId)
    -                                        )
    -                                    }
    -                                })
    -                            }
    -                            RoomListViewModel.SpaceFilterStrategy.ALL_IF_SPACE_NULL     -> {
    -                                activeSpaceUpdaters.add(object : RoomListViewModel.ActiveSpaceQueryUpdater {
    -                                    override fun updateForSpaceId(roomId: String?) {
    -                                        if (roomId != null) {
    -                                            it.queryParams = roomQueryParams.copy(
    -                                                    activeSpaceFilter = ActiveSpaceFilter.ActiveSpace(roomId)
    -                                            )
    -                                        } else {
    -                                            it.queryParams = roomQueryParams.copy(
    -                                                    activeSpaceFilter = ActiveSpaceFilter.None
    -                                            )
    -                                        }
    -                                    }
    -                                })
    -                            }
    -                            RoomListViewModel.SpaceFilterStrategy.NONE                  -> {
    -                                // we ignore current space for this one
    -                            }
    +        withQueryParams(query) { roomQueryParams ->
    +            val updatedQueryParams = roomQueryParams.process(spaceFilterStrategy, appStateHandler.safeActiveSpaceId())
    +            val liveQueryParams = MutableStateFlow(updatedQueryParams)
    +            val itemCountFlow = liveQueryParams
    +                    .flatMapLatest {
    +                        session.roomService().getRoomCountLive(it).asFlow()
    +                    }
    +                    .flowOn(Dispatchers.Main)
    +                    .distinctUntilChanged()
    +
    +            val name = stringProvider.getString(nameRes)
    +            val filteredPagedRoomSummariesLive = session.roomService().getFilteredPagedRoomSummariesLive(
    +                    roomQueryParams.process(spaceFilterStrategy, appStateHandler.safeActiveSpaceId()),
    +                    pagedListConfig
    +            )
    +            when (spaceFilterStrategy) {
    +                RoomListViewModel.SpaceFilterStrategy.ORPHANS_IF_SPACE_NULL -> {
    +                    activeSpaceUpdaters.add(object : RoomListViewModel.ActiveSpaceQueryUpdater {
    +                        override fun updateForSpaceId(roomId: String?) {
    +                            filteredPagedRoomSummariesLive.queryParams = roomQueryParams.copy(
    +                                    activeSpaceFilter = ActiveSpaceFilter.ActiveSpace(roomId)
    +                            )
    +                            liveQueryParams.update { filteredPagedRoomSummariesLive.queryParams }
                             }
    -                    }.livePagedList
    -                            .let { livePagedList ->
    -                                // use it also as a source to update count
    -                                livePagedList.asFlow()
    -                                        .onEach {
    -                                            Timber.v("Thread space list: ${Thread.currentThread()}")
    -                                            sections.find { it.sectionName == name }
    -                                                    ?.notificationCount
    -                                                    ?.postValue(
    -                                                            if (countRoomAsNotif) {
    -                                                                RoomAggregateNotificationCount(it.size, it.size)
    -                                                            } else {
    -                                                                session.getNotificationCountForRooms(
    -                                                                        roomQueryParams.process(spaceFilterStrategy, appStateHandler.safeActiveSpaceId())
    -                                                                )
    -                                                            }
    -                                                    )
    -                                        }
    -                                        .flowOn(Dispatchers.Default)
    -                                        .launchIn(viewModelScope)
    -
    -                                val itemCountFlow = livePagedList.asFlow()
    -                                        .flatMapLatest {
    -                                            val queryParams = roomQueryParams.process(spaceFilterStrategy, appStateHandler.safeActiveSpaceId())
    -                                            session.getRoomCountFlow(queryParams)
    -                                        }
    -                                        .distinctUntilChanged()
    -
    -                                sections.add(
    -                                        RoomsSection(
    -                                                sectionName = name,
    -                                                livePages = livePagedList,
    -                                                notifyOfLocalEcho = notifyOfLocalEcho,
    -                                                itemCount = itemCountFlow
    -                                        )
    +                    })
    +                }
    +                RoomListViewModel.SpaceFilterStrategy.ALL_IF_SPACE_NULL     -> {
    +                    activeSpaceUpdaters.add(object : RoomListViewModel.ActiveSpaceQueryUpdater {
    +                        override fun updateForSpaceId(roomId: String?) {
    +                            if (roomId != null) {
    +                                filteredPagedRoomSummariesLive.queryParams = roomQueryParams.copy(
    +                                        activeSpaceFilter = ActiveSpaceFilter.ActiveSpace(roomId)
    +                                )
    +                            } else {
    +                                filteredPagedRoomSummariesLive.queryParams = roomQueryParams.copy(
    +                                        activeSpaceFilter = ActiveSpaceFilter.None
                                     )
                                 }
    +                            liveQueryParams.update { filteredPagedRoomSummariesLive.queryParams }
    +                        }
    +                    })
                     }
    +                RoomListViewModel.SpaceFilterStrategy.NONE                  -> {
    +                    // we ignore current space for this one
    +                }
    +            }
     
    -        )
    +            val livePagedList = filteredPagedRoomSummariesLive.livePagedList
    +            // use it also as a source to update count
    +            livePagedList.asFlow()
    +                    .onEach {
    +                        Timber.v("Thread space list: ${Thread.currentThread()}")
    +                        sections.find { it.sectionName == name }
    +                                ?.notificationCount
    +                                ?.postValue(
    +                                        if (countRoomAsNotif) {
    +                                            RoomAggregateNotificationCount(it.size, it.size)
    +                                        } else {
    +                                            session.roomService().getNotificationCountForRooms(
    +                                                    roomQueryParams.process(spaceFilterStrategy, appStateHandler.safeActiveSpaceId())
    +                                            )
    +                                        }
    +                                )
    +                    }
    +                    .flowOn(Dispatchers.Default)
    +                    .launchIn(viewModelScope)
    +
    +            sections.add(
    +                    RoomsSection(
    +                            sectionName = name,
    +                            livePages = livePagedList,
    +                            notifyOfLocalEcho = notifyOfLocalEcho,
    +                            itemCount = itemCountFlow
    +                    )
    +            )
    +        }
         }
     
         private fun withQueryParams(builder: (RoomSummaryQueryParams.Builder) -> Unit, block: (RoomSummaryQueryParams) -> Unit) {
    diff --git a/vector/src/main/java/im/vector/app/features/home/room/list/RoomListViewModel.kt b/vector/src/main/java/im/vector/app/features/home/room/list/RoomListViewModel.kt
    index ec8b01876b..9fb13084d1 100644
    --- a/vector/src/main/java/im/vector/app/features/home/room/list/RoomListViewModel.kt
    +++ b/vector/src/main/java/im/vector/app/features/home/room/list/RoomListViewModel.kt
    @@ -29,7 +29,6 @@ import im.vector.app.AppStateHandler
     import im.vector.app.RoomGroupingMethod
     import im.vector.app.core.di.MavericksAssistedViewModelFactory
     import im.vector.app.core.di.hiltMavericksViewModelFactory
    -import im.vector.app.core.extensions.exhaustive
     import im.vector.app.core.platform.VectorViewModel
     import im.vector.app.core.resources.StringProvider
     import im.vector.app.features.analytics.AnalyticsTracker
    @@ -43,6 +42,7 @@ import kotlinx.coroutines.launch
     import org.matrix.android.sdk.api.extensions.orFalse
     import org.matrix.android.sdk.api.query.QueryStringValue
     import org.matrix.android.sdk.api.session.Session
    +import org.matrix.android.sdk.api.session.getRoom
     import org.matrix.android.sdk.api.session.room.UpdatableLivePageResult
     import org.matrix.android.sdk.api.session.room.members.ChangeMembershipState
     import org.matrix.android.sdk.api.session.room.model.tag.RoomTag
    @@ -163,7 +163,7 @@ class RoomListViewModel @AssistedInject constructor(
                 is RoomListAction.ToggleSection               -> handleToggleSection(action.section)
                 is RoomListAction.JoinSuggestedRoom           -> handleJoinSuggestedRoom(action)
                 is RoomListAction.ShowRoomDetails             -> handleShowRoomDetails(action)
    -        }.exhaustive
    +        }
         }
     
         fun isPublicRoom(roomId: String): Boolean {
    @@ -234,7 +234,7 @@ class RoomListViewModel @AssistedInject constructor(
     
             viewModelScope.launch {
                 try {
    -                session.leaveRoom(roomId)
    +                session.roomService().leaveRoom(roomId)
                     // We do not update the rejectingRoomsIds here, because, the room is not rejected yet regarding the sync data.
                     // Instead, we wait for the room to be rejected
                     // Known bug: if the user is invited again (after rejecting the first invitation), the loading will be displayed instead of the buttons.
    @@ -266,7 +266,7 @@ class RoomListViewModel @AssistedInject constructor(
     
             viewModelScope.launch {
                 try {
    -                session.joinRoom(action.roomId, null, action.viaServers ?: emptyList())
    +                session.roomService().joinRoom(action.roomId, null, action.viaServers ?: emptyList())
     
                     suggestedRoomJoiningState.postValue(suggestedRoomJoiningState.value.orEmpty().toMutableMap().apply {
                         this[action.roomId] = Success(Unit)
    @@ -320,7 +320,7 @@ class RoomListViewModel @AssistedInject constructor(
         private fun handleLeaveRoom(action: RoomListAction.LeaveRoom) {
             _viewEvents.post(RoomListViewEvents.Loading(null))
             viewModelScope.launch {
    -            val value = runCatching { session.leaveRoom(action.roomId) }
    +            val value = runCatching { session.roomService().leaveRoom(action.roomId) }
                         .fold({ RoomListViewEvents.Done }, { RoomListViewEvents.Failure(it) })
                 _viewEvents.post(value)
             }
    diff --git a/vector/src/main/java/im/vector/app/features/home/room/list/RoomSummaryItem.kt b/vector/src/main/java/im/vector/app/features/home/room/list/RoomSummaryItem.kt
    index b037191ad1..70c5846646 100644
    --- a/vector/src/main/java/im/vector/app/features/home/room/list/RoomSummaryItem.kt
    +++ b/vector/src/main/java/im/vector/app/features/home/room/list/RoomSummaryItem.kt
    @@ -38,7 +38,7 @@ import im.vector.app.features.displayname.getBestName
     import im.vector.app.features.home.AvatarRenderer
     import im.vector.app.features.themes.ThemeUtils
     import im.vector.lib.core.utils.epoxy.charsequence.EpoxyCharSequence
    -import org.matrix.android.sdk.api.crypto.RoomEncryptionTrustLevel
    +import org.matrix.android.sdk.api.session.crypto.model.RoomEncryptionTrustLevel
     import org.matrix.android.sdk.api.session.presence.model.UserPresence
     import org.matrix.android.sdk.api.util.MatrixItem
     
    diff --git a/vector/src/main/java/im/vector/app/features/home/room/list/RoomSummaryItemFactory.kt b/vector/src/main/java/im/vector/app/features/home/room/list/RoomSummaryItemFactory.kt
    index ca2a747b3b..6326d9c97a 100644
    --- a/vector/src/main/java/im/vector/app/features/home/room/list/RoomSummaryItemFactory.kt
    +++ b/vector/src/main/java/im/vector/app/features/home/room/list/RoomSummaryItemFactory.kt
    @@ -29,7 +29,6 @@ import im.vector.app.features.home.AvatarRenderer
     import im.vector.app.features.home.room.detail.timeline.format.DisplayableEventFormatter
     import im.vector.app.features.home.room.typing.TypingHelper
     import im.vector.lib.core.utils.epoxy.charsequence.toEpoxyCharSequence
    -import org.matrix.android.sdk.api.MatrixConfiguration
     import org.matrix.android.sdk.api.session.room.members.ChangeMembershipState
     import org.matrix.android.sdk.api.session.room.model.Membership
     import org.matrix.android.sdk.api.session.room.model.RoomSummary
    @@ -42,8 +41,7 @@ class RoomSummaryItemFactory @Inject constructor(private val displayableEventFor
                                                      private val stringProvider: StringProvider,
                                                      private val typingHelper: TypingHelper,
                                                      private val avatarRenderer: AvatarRenderer,
    -                                                 private val errorFormatter: ErrorFormatter,
    -                                                 private val matrixConfiguration: MatrixConfiguration) {
    +                                                 private val errorFormatter: ErrorFormatter) {
     
         fun create(roomSummary: RoomSummary,
                    roomChangeMembershipStates: Map,
    @@ -127,7 +125,7 @@ class RoomSummaryItemFactory @Inject constructor(private val displayableEventFor
                     // We do not display shield in the room list anymore
                     // .encryptionTrustLevel(roomSummary.roomEncryptionTrustLevel)
                     .izPublic(roomSummary.isPublic)
    -                .showPresence(roomSummary.isDirect && matrixConfiguration.presenceSyncEnabled)
    +                .showPresence(roomSummary.isDirect)
                     .userPresence(roomSummary.directUserPresence)
                     .matrixItem(roomSummary.toMatrixItem())
                     .lastEventTime(latestEventTime)
    diff --git a/vector/src/main/java/im/vector/app/features/home/room/list/SectionHeaderAdapter.kt b/vector/src/main/java/im/vector/app/features/home/room/list/SectionHeaderAdapter.kt
    index 2e6436d21d..3f1dfebf7b 100644
    --- a/vector/src/main/java/im/vector/app/features/home/room/list/SectionHeaderAdapter.kt
    +++ b/vector/src/main/java/im/vector/app/features/home/room/list/SectionHeaderAdapter.kt
    @@ -16,6 +16,7 @@
     
     package im.vector.app.features.home.room.list
     
    +import android.graphics.drawable.Drawable
     import android.view.LayoutInflater
     import android.view.ViewGroup
     import androidx.core.content.ContextCompat
    @@ -28,6 +29,7 @@ import im.vector.app.databinding.ItemRoomCategoryBinding
     import im.vector.app.features.themes.ThemeUtils
     
     class SectionHeaderAdapter constructor(
    +        roomsSectionData: RoomsSectionData,
             private val onClickAction: ClickListener
     ) : RecyclerView.Adapter() {
     
    @@ -39,14 +41,16 @@ class SectionHeaderAdapter constructor(
                 val isHighlighted: Boolean = false,
                 val isHidden: Boolean = true,
                 // This will be false until real data has been submitted once
    -            val isLoading: Boolean = true
    +            val isLoading: Boolean = true,
    +            val isCollapsable: Boolean = false
         )
     
    -    lateinit var roomsSectionData: RoomsSectionData
    +    var roomsSectionData: RoomsSectionData = roomsSectionData
             private set
     
    -    fun updateSection(newRoomsSectionData: RoomsSectionData) {
    -        if (!::roomsSectionData.isInitialized || newRoomsSectionData != roomsSectionData) {
    +    fun updateSection(block: (RoomsSectionData) -> RoomsSectionData) {
    +        val newRoomsSectionData = block(roomsSectionData)
    +        if (roomsSectionData != newRoomsSectionData) {
                 roomsSectionData = newRoomsSectionData
                 notifyDataSetChanged()
             }
    @@ -82,11 +86,16 @@ class SectionHeaderAdapter constructor(
             fun bind(roomsSectionData: RoomsSectionData) {
                 binding.roomCategoryTitleView.text = roomsSectionData.name
                 val tintColor = ThemeUtils.getColor(binding.root.context, R.attr.vctr_content_secondary)
    -            val expandedArrowDrawableRes = if (roomsSectionData.isExpanded) R.drawable.ic_expand_more else R.drawable.ic_expand_less
    -            val expandedArrowDrawable = ContextCompat.getDrawable(binding.root.context, expandedArrowDrawableRes)?.also {
    -                DrawableCompat.setTint(it, tintColor)
    +            val collapsableArrowDrawable: Drawable? = if (roomsSectionData.isCollapsable) {
    +                val expandedArrowDrawableRes = if (roomsSectionData.isExpanded) R.drawable.ic_expand_more else R.drawable.ic_expand_less
    +                ContextCompat.getDrawable(binding.root.context, expandedArrowDrawableRes)?.also {
    +                    DrawableCompat.setTint(it, tintColor)
    +                }
    +            } else {
    +                null
                 }
    -            binding.roomCategoryCounterView.setCompoundDrawablesWithIntrinsicBounds(null, null, expandedArrowDrawable, null)
    +            binding.root.isClickable = roomsSectionData.isCollapsable
    +            binding.roomCategoryCounterView.setCompoundDrawablesWithIntrinsicBounds(null, null, collapsableArrowDrawable, null)
                 binding.roomCategoryCounterView.text = roomsSectionData.itemCount.takeIf { it > 0 }?.toString().orEmpty()
                 binding.roomCategoryUnreadCounterBadgeView.render(UnreadCounterBadgeView.State(roomsSectionData.notificationCount, roomsSectionData.isHighlighted))
             }
    diff --git a/vector/src/main/java/im/vector/app/features/home/room/list/actions/RoomListQuickActionsSharedAction.kt b/vector/src/main/java/im/vector/app/features/home/room/list/actions/RoomListQuickActionsSharedAction.kt
    index 2d61da0dd5..625118919b 100644
    --- a/vector/src/main/java/im/vector/app/features/home/room/list/actions/RoomListQuickActionsSharedAction.kt
    +++ b/vector/src/main/java/im/vector/app/features/home/room/list/actions/RoomListQuickActionsSharedAction.kt
    @@ -25,7 +25,7 @@ sealed class RoomListQuickActionsSharedAction(
             @StringRes val titleRes: Int,
             @DrawableRes val iconResId: Int?,
             val destructive: Boolean = false) :
    -    VectorSharedAction {
    +        VectorSharedAction {
     
         data class NotificationsAllNoisy(val roomId: String) : RoomListQuickActionsSharedAction(
                 R.string.room_list_quick_actions_notifications_all_noisy,
    diff --git a/vector/src/main/java/im/vector/app/features/home/room/threads/ThreadsManager.kt b/vector/src/main/java/im/vector/app/features/home/room/threads/ThreadsManager.kt
    new file mode 100644
    index 0000000000..545077b550
    --- /dev/null
    +++ b/vector/src/main/java/im/vector/app/features/home/room/threads/ThreadsManager.kt
    @@ -0,0 +1,66 @@
    +/*
    + * Copyright (c) 2022 New Vector Ltd
    + *
    + * Licensed under the Apache License, Version 2.0 (the "License");
    + * you may not use this file except in compliance with the License.
    + * You may obtain a copy of the License at
    + *
    + *     http://www.apache.org/licenses/LICENSE-2.0
    + *
    + * Unless required by applicable law or agreed to in writing, software
    + * distributed under the License is distributed on an "AS IS" BASIS,
    + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    + * See the License for the specific language governing permissions and
    + * limitations under the License.
    + */
    +
    +package im.vector.app.features.home.room.threads
    +
    +import android.app.Activity
    +import android.text.Spanned
    +import androidx.annotation.StringRes
    +import androidx.core.text.HtmlCompat
    +import im.vector.app.R
    +import im.vector.app.core.resources.StringProvider
    +import im.vector.app.features.MainActivity
    +import im.vector.app.features.MainActivityArgs
    +import im.vector.app.features.settings.VectorPreferences
    +import org.matrix.android.sdk.api.settings.LightweightSettingsStorage
    +import javax.inject.Inject
    +
    +/**
    + * The class is responsible for handling thread specific tasks
    + */
    +class ThreadsManager @Inject constructor(
    +        private val vectorPreferences: VectorPreferences,
    +        private val lightweightSettingsStorage: LightweightSettingsStorage,
    +        private val stringProvider: StringProvider
    +) {
    +
    +    /**
    +     * Enable threads and invoke an initial sync. The initial sync is mandatory in order to change
    +     * the already saved DB schema for already received messages
    +     */
    +    fun enableThreadsAndRestart(activity: Activity) {
    +        vectorPreferences.setThreadMessagesEnabled()
    +        lightweightSettingsStorage.setThreadMessagesEnabled(vectorPreferences.areThreadMessagesEnabled())
    +        MainActivity.restartApp(activity, MainActivityArgs(clearCache = true))
    +    }
    +
    +    /**
    +     * Generates and return an Html spanned string to be rendered especially in dialogs
    +     */
    +    private fun generateLearnMoreHtmlString(@StringRes messageId: Int): Spanned {
    +        val learnMore = stringProvider.getString(R.string.action_learn_more)
    +        val learnMoreUrl = stringProvider.getString(R.string.threads_learn_more_url)
    +        val href = "$learnMore.

    " + val message = stringProvider.getString(messageId, href) + return HtmlCompat.fromHtml(message, HtmlCompat.FROM_HTML_MODE_LEGACY) + } + + fun getBetaEnableThreadsMessage(): Spanned = + generateLearnMoreHtmlString(R.string.threads_beta_enable_notice_message) + + fun getLabsEnableThreadsMessage(): Spanned = + generateLearnMoreHtmlString(R.string.threads_labs_enable_notice_message) +} diff --git a/vector/src/main/java/im/vector/app/features/home/room/threads/arguments/ThreadListArgs.kt b/vector/src/main/java/im/vector/app/features/home/room/threads/arguments/ThreadListArgs.kt index aa3746ea41..febd063202 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/threads/arguments/ThreadListArgs.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/threads/arguments/ThreadListArgs.kt @@ -18,7 +18,7 @@ package im.vector.app.features.home.room.threads.arguments import android.os.Parcelable import kotlinx.parcelize.Parcelize -import org.matrix.android.sdk.api.crypto.RoomEncryptionTrustLevel +import org.matrix.android.sdk.api.session.crypto.model.RoomEncryptionTrustLevel @Parcelize data class ThreadListArgs( diff --git a/vector/src/main/java/im/vector/app/features/home/room/threads/arguments/ThreadTimelineArgs.kt b/vector/src/main/java/im/vector/app/features/home/room/threads/arguments/ThreadTimelineArgs.kt index d3a80811ea..19419e52de 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/threads/arguments/ThreadTimelineArgs.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/threads/arguments/ThreadTimelineArgs.kt @@ -18,7 +18,7 @@ package im.vector.app.features.home.room.threads.arguments import android.os.Parcelable import kotlinx.parcelize.Parcelize -import org.matrix.android.sdk.api.crypto.RoomEncryptionTrustLevel +import org.matrix.android.sdk.api.session.crypto.model.RoomEncryptionTrustLevel @Parcelize data class ThreadTimelineArgs( diff --git a/vector/src/main/java/im/vector/app/features/home/room/threads/list/model/ThreadListItem.kt b/vector/src/main/java/im/vector/app/features/home/room/threads/list/model/ThreadListItem.kt index 2364e86166..385bb226a1 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/threads/list/model/ThreadListItem.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/threads/list/model/ThreadListItem.kt @@ -33,6 +33,7 @@ import im.vector.app.core.extensions.setLeftDrawable import im.vector.app.core.utils.DimensionConverter import im.vector.app.features.displayname.getBestName import im.vector.app.features.home.AvatarRenderer +import im.vector.app.features.themes.ThemeUtils import org.matrix.android.sdk.api.session.threads.ThreadNotificationState import org.matrix.android.sdk.api.util.MatrixItem @@ -60,9 +61,11 @@ abstract class ThreadListItem : VectorEpoxyModel() { holder.dateTextView.text = date if (rootMessageDeleted) { holder.rootMessageTextView.text = holder.view.context.getString(R.string.event_redacted) + holder.rootMessageTextView.setTextColor(ThemeUtils.getColor(holder.view.context, R.attr.vctr_content_secondary)) holder.rootMessageTextView.setLeftDrawable(R.drawable.ic_trash_16, R.attr.vctr_content_tertiary) holder.rootMessageTextView.compoundDrawablePadding = DimensionConverter(holder.view.context.resources).dpToPx(10) } else { + holder.rootMessageTextView.setTextColor(ThemeUtils.getColor(holder.view.context, R.attr.vctr_content_primary)) holder.rootMessageTextView.text = rootMessage holder.rootMessageTextView.clearDrawables() } diff --git a/vector/src/main/java/im/vector/app/features/home/room/threads/list/viewmodel/ThreadListController.kt b/vector/src/main/java/im/vector/app/features/home/room/threads/list/viewmodel/ThreadListController.kt index aeef69c6dc..6b3f0dc6b8 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/threads/list/viewmodel/ThreadListController.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/threads/list/viewmodel/ThreadListController.kt @@ -49,7 +49,7 @@ class ThreadListController @Inject constructor( } override fun buildModels() = - when (session.getHomeServerCapabilities().canUseThreading) { + when (session.homeServerCapabilitiesService().getHomeServerCapabilities().canUseThreading) { true -> buildThreadSummaries() false -> buildThreadList() } @@ -71,13 +71,13 @@ class ThreadListController @Inject constructor( } ?.forEach { threadSummary -> val date = dateFormatter.format(threadSummary.latestEvent?.originServerTs, DateFormatKind.ROOM_LIST) - val lastMessageFormatted = threadSummary.let { + val lastMessageFormatted = threadSummary.let { displayableEventFormatter.formatThreadSummary( event = it.latestEvent, latestEdition = it.threadEditions.latestThreadEdition ).toString() } - val rootMessageFormatted = threadSummary.let { + val rootMessageFormatted = threadSummary.let { displayableEventFormatter.formatThreadSummary( event = it.rootEvent, latestEdition = it.threadEditions.rootThreadEdition @@ -123,12 +123,12 @@ class ThreadListController @Inject constructor( ?.forEach { timelineEvent -> val date = dateFormatter.format(timelineEvent.root.threadDetails?.lastMessageTimestamp, DateFormatKind.ROOM_LIST) val lastRootThreadEdition = timelineEvent.root.threadDetails?.lastRootThreadEdition - val lastMessageFormatted = timelineEvent.root.threadDetails?.threadSummaryLatestEvent.let { + val lastMessageFormatted = timelineEvent.root.threadDetails?.threadSummaryLatestEvent.let { displayableEventFormatter.formatThreadSummary( event = it, ).toString() } - val rootMessageFormatted = timelineEvent.root.let { + val rootMessageFormatted = timelineEvent.root.let { displayableEventFormatter.formatThreadSummary( event = it, latestEdition = lastRootThreadEdition diff --git a/vector/src/main/java/im/vector/app/features/home/room/threads/list/viewmodel/ThreadListViewModel.kt b/vector/src/main/java/im/vector/app/features/home/room/threads/list/viewmodel/ThreadListViewModel.kt index 7f18d172e4..a022ca19f3 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/threads/list/viewmodel/ThreadListViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/threads/list/viewmodel/ThreadListViewModel.kt @@ -33,6 +33,7 @@ import kotlinx.coroutines.flow.flowOn import kotlinx.coroutines.flow.map import kotlinx.coroutines.launch import org.matrix.android.sdk.api.session.Session +import org.matrix.android.sdk.api.session.getRoom import org.matrix.android.sdk.api.session.threads.ThreadTimelineEvent import org.matrix.android.sdk.flow.flow @@ -68,7 +69,7 @@ class ThreadListViewModel @AssistedInject constructor(@Assisted val initialState * capabilities */ private fun fetchAndObserveThreads() { - when (session.getHomeServerCapabilities().canUseThreading) { + when (session.homeServerCapabilitiesService().getHomeServerCapabilities().canUseThreading) { true -> { fetchThreadList() observeThreadSummaries() @@ -113,11 +114,19 @@ class ThreadListViewModel @AssistedInject constructor(@Assisted val initialState private fun fetchThreadList() { viewModelScope.launch { + setLoading(true) room?.fetchThreadSummaries() + setLoading(false) } } - fun canHomeserverUseThreading() = session.getHomeServerCapabilities().canUseThreading + private fun setLoading(isLoading: Boolean) { + setState { + copy(isLoading = isLoading) + } + } + + fun canHomeserverUseThreading() = session.homeServerCapabilitiesService().getHomeServerCapabilities().canUseThreading fun applyFiltering(shouldFilterThreads: Boolean) { analyticsTracker.capture(Interaction.Name.MobileThreadListFilterItem.toAnalyticsInteraction()) diff --git a/vector/src/main/java/im/vector/app/features/home/room/threads/list/viewmodel/ThreadListViewState.kt b/vector/src/main/java/im/vector/app/features/home/room/threads/list/viewmodel/ThreadListViewState.kt index e08f70030b..2328da0b8a 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/threads/list/viewmodel/ThreadListViewState.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/threads/list/viewmodel/ThreadListViewState.kt @@ -27,6 +27,7 @@ data class ThreadListViewState( val threadSummaryList: Async> = Uninitialized, val rootThreadEventList: Async> = Uninitialized, val shouldFilterThreads: Boolean = false, + val isLoading: Boolean = false, val roomId: String ) : MavericksState { constructor(args: ThreadListArgs) : this(roomId = args.roomId) diff --git a/vector/src/main/java/im/vector/app/features/home/room/threads/list/views/ThreadListBottomSheet.kt b/vector/src/main/java/im/vector/app/features/home/room/threads/list/views/ThreadListBottomSheet.kt index 7ad4804e5b..07684a796e 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/threads/list/views/ThreadListBottomSheet.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/threads/list/views/ThreadListBottomSheet.kt @@ -61,11 +61,11 @@ class ThreadListBottomSheet : VectorBaseBottomSheetDialogFragment(), @@ -76,10 +80,20 @@ class ThreadListFragment @Inject constructor( } } + override fun onPrepareOptionsMenu(menu: Menu) { + withState(threadListViewModel) { state -> + when (threadListViewModel.canHomeserverUseThreading()) { + true -> menu.findItem(R.id.menu_thread_list_filter).isVisible = !state.threadSummaryList.invoke().isNullOrEmpty() + false -> menu.findItem(R.id.menu_thread_list_filter).isVisible = !state.rootThreadEventList.invoke().isNullOrEmpty() + } + } + } + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) initToolbar() initTextConstants() + initBetaFeedback() views.threadListRecyclerView.configureWith(threadListController, TimelineItemAnimator(), hasFixedSize = false) threadListController.listener = this } @@ -101,9 +115,23 @@ class ThreadListFragment @Inject constructor( resources.getString(R.string.reply_in_thread)) } + private fun initBetaFeedback() { + views.threadsFeedBackConstraintLayout.isVisible = resources.getBoolean(R.bool.feature_threads_beta_feedback_enabled) + views.threadFeedbackDivider.isVisible = resources.getBoolean(R.bool.feature_threads_beta_feedback_enabled) + views.threadsFeedBackConstraintLayout.debouncedClicks { + bugReporter.openBugReportScreen(requireActivity(), reportType = ReportType.THREADS_BETA_FEEDBACK) + } + } + override fun invalidate() = withState(threadListViewModel) { state -> + invalidateOptionsMenu() renderEmptyStateIfNeeded(state) threadListController.update(state) + renderLoaderIfNeeded(state) + } + + private fun renderLoaderIfNeeded(state: ThreadListViewState) { + views.threadListProgressBar.isVisible = state.isLoading } private fun renderToolbar() { diff --git a/vector/src/main/java/im/vector/app/features/homeserver/HomeServerCapabilitiesViewModel.kt b/vector/src/main/java/im/vector/app/features/homeserver/HomeServerCapabilitiesViewModel.kt index 9223485eff..1e4fccefd5 100644 --- a/vector/src/main/java/im/vector/app/features/homeserver/HomeServerCapabilitiesViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/homeserver/HomeServerCapabilitiesViewModel.kt @@ -53,7 +53,7 @@ class HomeServerCapabilitiesViewModel @AssistedInject constructor( override fun initialState(viewModelContext: ViewModelContext): HomeServerCapabilitiesViewState { val session = EntryPoints.get(viewModelContext.app(), SingletonEntryPoint::class.java).activeSessionHolder().getSafeActiveSession() return HomeServerCapabilitiesViewState( - capabilities = session?.getHomeServerCapabilities() ?: HomeServerCapabilities() + capabilities = session?.homeServerCapabilitiesService()?.getHomeServerCapabilities() ?: HomeServerCapabilities() ) } } diff --git a/vector/src/main/java/im/vector/app/features/html/PillsPostProcessor.kt b/vector/src/main/java/im/vector/app/features/html/PillsPostProcessor.kt index 506f5e773c..2d63d69c35 100644 --- a/vector/src/main/java/im/vector/app/features/html/PillsPostProcessor.kt +++ b/vector/src/main/java/im/vector/app/features/html/PillsPostProcessor.kt @@ -26,6 +26,8 @@ import im.vector.app.core.di.ActiveSessionHolder import im.vector.app.core.glide.GlideApp import im.vector.app.features.home.AvatarRenderer import io.noties.markwon.core.spans.LinkSpan +import org.matrix.android.sdk.api.session.getRoomSummary +import org.matrix.android.sdk.api.session.getUser import org.matrix.android.sdk.api.session.permalinks.PermalinkData import org.matrix.android.sdk.api.session.permalinks.PermalinkParser import org.matrix.android.sdk.api.session.room.model.RoomSummary @@ -100,7 +102,7 @@ class PillsPostProcessor @AssistedInject constructor(@Assisted private val roomI if (roomId == null) { sessionHolder.getSafeActiveSession()?.getUser(userId)?.toMatrixItem() } else { - sessionHolder.getSafeActiveSession()?.getRoomMember(userId, roomId)?.toMatrixItem() + sessionHolder.getSafeActiveSession()?.roomService()?.getRoomMember(userId, roomId)?.toMatrixItem() } private fun PermalinkData.RoomLink.toMatrixItem(): MatrixItem? = @@ -116,7 +118,7 @@ class PillsPostProcessor @AssistedInject constructor(@Assisted private val roomI } private fun PermalinkData.GroupLink.toMatrixItem(): MatrixItem? { - val group = sessionHolder.getSafeActiveSession()?.getGroupSummary(groupId) + val group = sessionHolder.getSafeActiveSession()?.groupService()?.getGroupSummary(groupId) return MatrixItem.GroupItem(groupId, group?.displayName, group?.avatarUrl) } } diff --git a/vector/src/main/java/im/vector/app/features/invite/InviteUsersToRoomActivity.kt b/vector/src/main/java/im/vector/app/features/invite/InviteUsersToRoomActivity.kt index 48a70fb164..7bb6670e96 100644 --- a/vector/src/main/java/im/vector/app/features/invite/InviteUsersToRoomActivity.kt +++ b/vector/src/main/java/im/vector/app/features/invite/InviteUsersToRoomActivity.kt @@ -74,8 +74,7 @@ class InviteUsersToRoomActivity : SimpleFragmentActivity() { is UserListSharedAction.OnMenuItemSelected -> onMenuItemSelected(sharedAction) UserListSharedAction.OpenPhoneBook -> openPhoneBook() // not exhaustive because it's a sharedAction - else -> { - } + else -> Unit } } .launchIn(lifecycleScope) diff --git a/vector/src/main/java/im/vector/app/features/invite/InviteUsersToRoomViewModel.kt b/vector/src/main/java/im/vector/app/features/invite/InviteUsersToRoomViewModel.kt index 891194040e..a7745c1681 100644 --- a/vector/src/main/java/im/vector/app/features/invite/InviteUsersToRoomViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/invite/InviteUsersToRoomViewModel.kt @@ -28,16 +28,16 @@ import im.vector.app.core.resources.StringProvider import im.vector.app.features.userdirectory.PendingSelection import kotlinx.coroutines.flow.asFlow import kotlinx.coroutines.flow.catch -import kotlinx.coroutines.flow.collect import kotlinx.coroutines.flow.map import kotlinx.coroutines.launch import org.matrix.android.sdk.api.session.Session +import org.matrix.android.sdk.api.session.getRoom -class InviteUsersToRoomViewModel @AssistedInject constructor(@Assisted - initialState: InviteUsersToRoomViewState, - session: Session, - val stringProvider: StringProvider) : - VectorViewModel(initialState) { +class InviteUsersToRoomViewModel @AssistedInject constructor( + @Assisted initialState: InviteUsersToRoomViewState, + session: Session, + val stringProvider: StringProvider +) : VectorViewModel(initialState) { private val room = session.getRoom(initialState.roomId)!! diff --git a/vector/src/main/java/im/vector/app/features/invite/InvitesAcceptor.kt b/vector/src/main/java/im/vector/app/features/invite/InvitesAcceptor.kt index a482998f77..bdc9ee2d1a 100644 --- a/vector/src/main/java/im/vector/app/features/invite/InvitesAcceptor.kt +++ b/vector/src/main/java/im/vector/app/features/invite/InvitesAcceptor.kt @@ -34,6 +34,7 @@ import kotlinx.coroutines.sync.withPermit import org.matrix.android.sdk.api.extensions.orFalse import org.matrix.android.sdk.api.failure.Failure import org.matrix.android.sdk.api.session.Session +import org.matrix.android.sdk.api.session.getRoom import org.matrix.android.sdk.api.session.room.members.ChangeMembershipState import org.matrix.android.sdk.api.session.room.model.Membership import org.matrix.android.sdk.api.session.room.roomSummaryQueryParams @@ -111,11 +112,11 @@ class InvitesAcceptor @Inject constructor( rejectInviteSafely(roomId) return } - val roomMembershipChanged = getChangeMemberships(roomId) + val roomMembershipChanged = roomService().getChangeMemberships(roomId) if (roomMembershipChanged != ChangeMembershipState.Joined && !roomMembershipChanged.isInProgress()) { try { Timber.v("Try auto join room: $roomId") - joinRoom(roomId) + roomService().joinRoom(roomId) } catch (failure: Throwable) { Timber.v("Failed auto join room: $roomId") // if we got 404 on invites, the inviting user have left or the hs is off. @@ -134,7 +135,7 @@ class InvitesAcceptor @Inject constructor( private suspend fun Session.rejectInviteSafely(roomId: String) { try { - leaveRoom(roomId) + roomService().leaveRoom(roomId) shouldRejectRoomIds.remove(roomId) } catch (failure: Throwable) { Timber.v("Fail rejecting invite for room: $roomId") diff --git a/vector/src/main/java/im/vector/app/features/invite/VectorInviteView.kt b/vector/src/main/java/im/vector/app/features/invite/VectorInviteView.kt index d9f1ad343b..7c5159ce11 100644 --- a/vector/src/main/java/im/vector/app/features/invite/VectorInviteView.kt +++ b/vector/src/main/java/im/vector/app/features/invite/VectorInviteView.kt @@ -32,7 +32,7 @@ import javax.inject.Inject @AndroidEntryPoint class VectorInviteView @JvmOverloads constructor(context: Context, attrs: AttributeSet? = null, defStyle: Int = 0) : - ConstraintLayout(context, attrs, defStyle) { + ConstraintLayout(context, attrs, defStyle) { interface Callback { fun onAcceptInvite() diff --git a/vector/src/main/java/im/vector/app/features/link/LinkHandlerActivity.kt b/vector/src/main/java/im/vector/app/features/link/LinkHandlerActivity.kt index 2cb41784b7..5b44f69821 100644 --- a/vector/src/main/java/im/vector/app/features/link/LinkHandlerActivity.kt +++ b/vector/src/main/java/im/vector/app/features/link/LinkHandlerActivity.kt @@ -133,7 +133,7 @@ class LinkHandlerActivity : VectorBaseActivity() { } else { lifecycleScope.launch { try { - session.signOut(true) + session.signOutService().signOut(true) Timber.d("## displayAlreadyLoginPopup(): logout succeeded") sessionHolder.clearActiveSession() startLoginActivity(uri) diff --git a/vector/src/main/java/im/vector/app/features/location/LocationSharingAction.kt b/vector/src/main/java/im/vector/app/features/location/LocationSharingAction.kt index d7d686ee60..d86d97e6b0 100644 --- a/vector/src/main/java/im/vector/app/features/location/LocationSharingAction.kt +++ b/vector/src/main/java/im/vector/app/features/location/LocationSharingAction.kt @@ -23,5 +23,5 @@ sealed class LocationSharingAction : VectorViewModelAction { data class PinnedLocationSharing(val locationData: LocationData?) : LocationSharingAction() data class LocationTargetChange(val locationData: LocationData) : LocationSharingAction() object ZoomToUserLocation : LocationSharingAction() - object StartLiveLocationSharing : LocationSharingAction() + data class StartLiveLocationSharing(val durationMillis: Long) : LocationSharingAction() } diff --git a/vector/src/main/java/im/vector/app/features/location/LocationSharingFragment.kt b/vector/src/main/java/im/vector/app/features/location/LocationSharingFragment.kt index c4dccc1b73..38b96142b5 100644 --- a/vector/src/main/java/im/vector/app/features/location/LocationSharingFragment.kt +++ b/vector/src/main/java/im/vector/app/features/location/LocationSharingFragment.kt @@ -16,28 +16,31 @@ package im.vector.app.features.location +import android.content.Intent import android.graphics.drawable.Drawable import android.os.Bundle import android.view.LayoutInflater import android.view.View import android.view.ViewGroup +import androidx.core.content.ContextCompat import androidx.core.view.isGone import androidx.lifecycle.lifecycleScope import com.airbnb.mvrx.fragmentViewModel import com.airbnb.mvrx.withState import com.google.android.material.dialog.MaterialAlertDialogBuilder import com.mapbox.mapboxsdk.maps.MapView -import im.vector.app.BuildConfig import im.vector.app.R -import im.vector.app.core.extensions.exhaustive +import im.vector.app.core.platform.VectorBaseBottomSheetDialogFragment import im.vector.app.core.platform.VectorBaseFragment import im.vector.app.core.utils.PERMISSIONS_FOR_BACKGROUND_LOCATION_SHARING import im.vector.app.core.utils.PERMISSIONS_FOR_FOREGROUND_LOCATION_SHARING import im.vector.app.core.utils.checkPermissions import im.vector.app.core.utils.registerForPermissionsResult import im.vector.app.databinding.FragmentLocationSharingBinding +import im.vector.app.features.VectorFeatures import im.vector.app.features.home.AvatarRenderer import im.vector.app.features.home.room.detail.timeline.helper.MatrixItemColorProvider +import im.vector.app.features.location.live.duration.ChooseLiveDurationBottomSheet import im.vector.app.features.location.option.LocationSharingOption import org.matrix.android.sdk.api.util.MatrixItem import java.lang.ref.WeakReference @@ -49,8 +52,11 @@ import javax.inject.Inject class LocationSharingFragment @Inject constructor( private val urlMapProvider: UrlMapProvider, private val avatarRenderer: AvatarRenderer, - private val matrixItemColorProvider: MatrixItemColorProvider -) : VectorBaseFragment(), LocationTargetChangeListener { + private val matrixItemColorProvider: MatrixItemColorProvider, + private val vectorFeatures: VectorFeatures, +) : VectorBaseFragment(), + LocationTargetChangeListener, + VectorBaseBottomSheetDialogFragment.ResultListener { private val viewModel: LocationSharingViewModel by fragmentViewModel() @@ -83,10 +89,11 @@ class LocationSharingFragment @Inject constructor( viewModel.observeViewEvents { when (it) { - LocationSharingViewEvents.Close -> locationSharingNavigator.quit() - LocationSharingViewEvents.LocationNotAvailableError -> handleLocationNotAvailableError() - is LocationSharingViewEvents.ZoomToUserLocation -> handleZoomToUserLocationEvent(it) - }.exhaustive + LocationSharingViewEvents.Close -> locationSharingNavigator.quit() + LocationSharingViewEvents.LocationNotAvailableError -> handleLocationNotAvailableError() + is LocationSharingViewEvents.ZoomToUserLocation -> handleZoomToUserLocationEvent(it) + is LocationSharingViewEvents.StartLiveLocationService -> handleStartLiveLocationService(it) + } } } @@ -177,6 +184,18 @@ class LocationSharingFragment @Inject constructor( views.mapView.zoomToLocation(event.userLocation.latitude, event.userLocation.longitude) } + private fun handleStartLiveLocationService(event: LocationSharingViewEvents.StartLiveLocationService) { + val args = LocationSharingService.RoomArgs(event.sessionId, event.roomId, event.durationMillis) + + Intent(requireContext(), LocationSharingService::class.java) + .putExtra(LocationSharingService.EXTRA_ROOM_ARGS, args) + .also { + ContextCompat.startForegroundService(requireContext(), it) + } + + vectorBaseActivity.finish() + } + private fun initOptionsPicker() { // set no option at start views.shareLocationOptionsPicker.render() @@ -222,14 +241,21 @@ class LocationSharingFragment @Inject constructor( } private fun startLiveLocationSharing() { - viewModel.handle(LocationSharingAction.StartLiveLocationSharing) + ChooseLiveDurationBottomSheet.newInstance(this) + .show(requireActivity().supportFragmentManager, "DISPLAY_CHOOSE_DURATION_OPTIONS") + } + + override fun onBottomSheetResult(resultCode: Int, data: Any?) { + if (resultCode == VectorBaseBottomSheetDialogFragment.ResultListener.RESULT_OK) { + (data as? Long)?.let { viewModel.handle(LocationSharingAction.StartLiveLocationSharing(it)) } + } } private fun updateMap(state: LocationSharingViewState) { // first, update the options view val options: Set = when (state.areTargetAndUserLocationEqual) { true -> { - if (BuildConfig.ENABLE_LIVE_LOCATION_SHARING) { + if (vectorFeatures.isLiveLocationEnabled()) { setOf(LocationSharingOption.USER_CURRENT, LocationSharingOption.USER_LIVE) } else { setOf(LocationSharingOption.USER_CURRENT) diff --git a/vector/src/main/java/im/vector/app/features/location/LocationSharingService.kt b/vector/src/main/java/im/vector/app/features/location/LocationSharingService.kt new file mode 100644 index 0000000000..938cef6825 --- /dev/null +++ b/vector/src/main/java/im/vector/app/features/location/LocationSharingService.kt @@ -0,0 +1,218 @@ +/* + * 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.location + +import android.content.Intent +import android.os.Binder +import android.os.IBinder +import android.os.Parcelable +import dagger.hilt.android.AndroidEntryPoint +import im.vector.app.core.di.ActiveSessionHolder +import im.vector.app.core.services.VectorService +import im.vector.app.core.time.Clock +import im.vector.app.features.notifications.NotificationUtils +import im.vector.app.features.session.coroutineScope +import kotlinx.coroutines.launch +import kotlinx.parcelize.Parcelize +import org.matrix.android.sdk.api.session.Session +import org.matrix.android.sdk.api.session.events.model.EventType +import org.matrix.android.sdk.api.session.events.model.toContent +import org.matrix.android.sdk.api.session.getRoom +import org.matrix.android.sdk.api.session.room.model.livelocation.LiveLocationBeaconContent +import timber.log.Timber +import java.util.Timer +import java.util.TimerTask +import javax.inject.Inject + +@AndroidEntryPoint +class LocationSharingService : VectorService(), LocationTracker.Callback { + + @Parcelize + data class RoomArgs( + val sessionId: String, + val roomId: String, + val durationMillis: Long + ) : Parcelable + + @Inject lateinit var notificationUtils: NotificationUtils + @Inject lateinit var locationTracker: LocationTracker + @Inject lateinit var activeSessionHolder: ActiveSessionHolder + @Inject lateinit var clock: Clock + + private val binder = LocalBinder() + + private var roomArgsList = mutableListOf() + private var timers = mutableListOf() + + override fun onCreate() { + super.onCreate() + Timber.i("### LocationSharingService.onCreate") + + // Start tracking location + locationTracker.addCallback(this) + locationTracker.start() + } + + override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int { + val roomArgs = intent?.getParcelableExtra(EXTRA_ROOM_ARGS) as? RoomArgs + + Timber.i("### LocationSharingService.onStartCommand. sessionId - roomId ${roomArgs?.sessionId} - ${roomArgs?.roomId}") + + if (roomArgs != null) { + roomArgsList.add(roomArgs) + + // Show a sticky notification + val notification = notificationUtils.buildLiveLocationSharingNotification() + startForeground(roomArgs.roomId.hashCode(), notification) + + // Schedule a timer to stop sharing + scheduleTimer(roomArgs.roomId, roomArgs.durationMillis) + + // Send beacon info state event + activeSessionHolder + .getSafeActiveSession() + ?.let { session -> + session.coroutineScope.launch(session.coroutineDispatchers.io) { + sendLiveBeaconInfo(session, roomArgs) + } + } + } + + return START_STICKY + } + + private suspend fun sendLiveBeaconInfo(session: Session, roomArgs: RoomArgs) { + val beaconContent = LiveLocationBeaconContent( + timeout = roomArgs.durationMillis, + isLive = true, + unstableTimestampAsMilliseconds = clock.epochMillis() + ).toContent() + + val stateKey = session.myUserId + session + .getRoom(roomArgs.roomId) + ?.sendStateEvent( + eventType = EventType.STATE_ROOM_BEACON_INFO.first(), + stateKey = stateKey, + body = beaconContent + ) + } + + private fun scheduleTimer(roomId: String, durationMillis: Long) { + Timer() + .apply { + schedule(object : TimerTask() { + override fun run() { + stopSharingLocation(roomId) + timers.remove(this@apply) + } + }, durationMillis) + } + .also { + timers.add(it) + } + } + + fun stopSharingLocation(roomId: String) { + Timber.i("### LocationSharingService.stopSharingLocation for $roomId") + + // Send a new beacon info state by setting live field as false + sendStoppedBeaconInfo(roomId) + + synchronized(roomArgsList) { + roomArgsList.removeAll { it.roomId == roomId } + if (roomArgsList.isEmpty()) { + Timber.i("### LocationSharingService. Destroying self, time is up for all rooms") + destroyMe() + } + } + } + + private fun sendStoppedBeaconInfo(roomId: String) { + activeSessionHolder + .getSafeActiveSession() + ?.let { session -> + session.coroutineScope.launch(session.coroutineDispatchers.io) { + session.getRoom(roomId)?.stopLiveLocation(session.myUserId) + } + } + } + + override fun onLocationUpdate(locationData: LocationData) { + Timber.i("### LocationSharingService.onLocationUpdate. Uncertainty: ${locationData.uncertainty}") + + val session = activeSessionHolder.getSafeActiveSession() + // Emit location update to all rooms in which live location sharing is active + session?.coroutineScope?.launch(session.coroutineDispatchers.io) { + roomArgsList.toList().forEach { roomArg -> + sendLiveLocation(roomArg.roomId, locationData) + } + } + } + + private suspend fun sendLiveLocation(roomId: String, locationData: LocationData) { + val session = activeSessionHolder.getSafeActiveSession() + val room = session?.getRoom(roomId) + val userId = session?.myUserId + + if (room == null || userId == null) { + return + } + + room + .getLiveLocationBeaconInfo(userId, true) + ?.eventId + ?.let { + room.sendLiveLocation( + beaconInfoEventId = it, + latitude = locationData.latitude, + longitude = locationData.longitude, + uncertainty = locationData.uncertainty + ) + } + } + + override fun onLocationProviderIsNotAvailable() { + stopForeground(true) + stopSelf() + } + + private fun destroyMe() { + locationTracker.removeCallback(this) + timers.forEach { it.cancel() } + timers.clear() + stopSelf() + } + + override fun onDestroy() { + super.onDestroy() + Timber.i("### LocationSharingService.onDestroy") + destroyMe() + } + + override fun onBind(intent: Intent?): IBinder { + return binder + } + + inner class LocalBinder : Binder() { + fun getService(): LocationSharingService = this@LocationSharingService + } + + companion object { + const val EXTRA_ROOM_ARGS = "EXTRA_ROOM_ARGS" + } +} diff --git a/vector/src/main/java/im/vector/app/features/location/LocationSharingServiceConnection.kt b/vector/src/main/java/im/vector/app/features/location/LocationSharingServiceConnection.kt new file mode 100644 index 0000000000..e72f77531b --- /dev/null +++ b/vector/src/main/java/im/vector/app/features/location/LocationSharingServiceConnection.kt @@ -0,0 +1,70 @@ +/* + * 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.location + +import android.content.ComponentName +import android.content.Context +import android.content.Intent +import android.content.ServiceConnection +import android.os.IBinder +import javax.inject.Inject + +class LocationSharingServiceConnection @Inject constructor( + private val context: Context +) : ServiceConnection { + + interface Callback { + fun onLocationServiceRunning() + fun onLocationServiceStopped() + } + + private var callback: Callback? = null + private var isBound = false + private var locationSharingService: LocationSharingService? = null + + fun bind(callback: Callback) { + this.callback = callback + + if (isBound) { + callback.onLocationServiceRunning() + } else { + Intent(context, LocationSharingService::class.java).also { intent -> + context.bindService(intent, this, 0) + } + } + } + + fun unbind() { + callback = null + } + + fun stopLiveLocationSharing(roomId: String) { + locationSharingService?.stopSharingLocation(roomId) + } + + override fun onServiceConnected(className: ComponentName, binder: IBinder) { + locationSharingService = (binder as LocationSharingService.LocalBinder).getService() + isBound = true + callback?.onLocationServiceRunning() + } + + override fun onServiceDisconnected(className: ComponentName) { + isBound = false + locationSharingService = null + callback?.onLocationServiceStopped() + } +} diff --git a/vector/src/main/java/im/vector/app/features/location/LocationSharingViewEvents.kt b/vector/src/main/java/im/vector/app/features/location/LocationSharingViewEvents.kt index 8d31db1119..1116003e41 100644 --- a/vector/src/main/java/im/vector/app/features/location/LocationSharingViewEvents.kt +++ b/vector/src/main/java/im/vector/app/features/location/LocationSharingViewEvents.kt @@ -22,4 +22,5 @@ sealed class LocationSharingViewEvents : VectorViewEvents { object Close : LocationSharingViewEvents() object LocationNotAvailableError : LocationSharingViewEvents() data class ZoomToUserLocation(val userLocation: LocationData) : LocationSharingViewEvents() + data class StartLiveLocationService(val sessionId: String, val roomId: String, val durationMillis: Long) : LocationSharingViewEvents() } diff --git a/vector/src/main/java/im/vector/app/features/location/LocationSharingViewModel.kt b/vector/src/main/java/im/vector/app/features/location/LocationSharingViewModel.kt index 639666e63f..34cb68973c 100644 --- a/vector/src/main/java/im/vector/app/features/location/LocationSharingViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/location/LocationSharingViewModel.kt @@ -23,7 +23,6 @@ import dagger.assisted.AssistedFactory import dagger.assisted.AssistedInject import im.vector.app.core.di.MavericksAssistedViewModelFactory import im.vector.app.core.di.hiltMavericksViewModelFactory -import im.vector.app.core.extensions.exhaustive import im.vector.app.core.platform.VectorViewModel import im.vector.app.features.home.room.detail.timeline.helper.LocationPinProvider import im.vector.app.features.location.domain.usecase.CompareLocationsUseCase @@ -37,8 +36,9 @@ import kotlinx.coroutines.flow.sample import kotlinx.coroutines.launch import org.matrix.android.sdk.api.extensions.orFalse import org.matrix.android.sdk.api.session.Session +import org.matrix.android.sdk.api.session.getRoom +import org.matrix.android.sdk.api.session.getUser import org.matrix.android.sdk.api.util.toMatrixItem -import timber.log.Timber /** * Sampling period to compare target location and user location. @@ -50,7 +50,7 @@ class LocationSharingViewModel @AssistedInject constructor( private val locationTracker: LocationTracker, private val locationPinProvider: LocationPinProvider, private val session: Session, - private val compareLocationsUseCase: CompareLocationsUseCase + private val compareLocationsUseCase: CompareLocationsUseCase, ) : VectorViewModel(initialState), LocationTracker.Callback { private val room = session.getRoom(initialState.roomId)!! @@ -65,7 +65,8 @@ class LocationSharingViewModel @AssistedInject constructor( companion object : MavericksViewModelFactory by hiltMavericksViewModelFactory() init { - locationTracker.start(this) + locationTracker.addCallback(this) + locationTracker.start() setUserItem() updatePin() compareTargetAndUserLocation() @@ -112,17 +113,17 @@ class LocationSharingViewModel @AssistedInject constructor( override fun onCleared() { super.onCleared() - locationTracker.stop() + locationTracker.removeCallback(this) } override fun handle(action: LocationSharingAction) { when (action) { - LocationSharingAction.CurrentUserLocationSharing -> handleCurrentUserLocationSharingAction() - is LocationSharingAction.PinnedLocationSharing -> handlePinnedLocationSharingAction(action) - is LocationSharingAction.LocationTargetChange -> handleLocationTargetChangeAction(action) - LocationSharingAction.ZoomToUserLocation -> handleZoomToUserLocationAction() - LocationSharingAction.StartLiveLocationSharing -> handleStartLiveLocationSharingAction() - }.exhaustive + LocationSharingAction.CurrentUserLocationSharing -> handleCurrentUserLocationSharingAction() + is LocationSharingAction.PinnedLocationSharing -> handlePinnedLocationSharingAction(action) + is LocationSharingAction.LocationTargetChange -> handleLocationTargetChangeAction(action) + LocationSharingAction.ZoomToUserLocation -> handleZoomToUserLocationAction() + is LocationSharingAction.StartLiveLocationSharing -> handleStartLiveLocationSharingAction(action.durationMillis) + } } private fun handleCurrentUserLocationSharingAction() = withState { state -> @@ -159,9 +160,12 @@ class LocationSharingViewModel @AssistedInject constructor( } } - private fun handleStartLiveLocationSharingAction() { - // TODO start sharing live location and update view state - Timber.d("live location sharing started") + private fun handleStartLiveLocationSharingAction(durationMillis: Long) { + _viewEvents.post(LocationSharingViewEvents.StartLiveLocationService( + sessionId = session.sessionId, + roomId = room.roomId, + durationMillis = durationMillis + )) } override fun onLocationUpdate(locationData: LocationData) { diff --git a/vector/src/main/java/im/vector/app/features/location/LocationSharingViewState.kt b/vector/src/main/java/im/vector/app/features/location/LocationSharingViewState.kt index ee5ba402e2..64f324bc1b 100644 --- a/vector/src/main/java/im/vector/app/features/location/LocationSharingViewState.kt +++ b/vector/src/main/java/im/vector/app/features/location/LocationSharingViewState.kt @@ -39,7 +39,7 @@ data class LocationSharingViewState( constructor(locationSharingArgs: LocationSharingArgs) : this( roomId = locationSharingArgs.roomId, - mode = locationSharingArgs.mode + mode = locationSharingArgs.mode, ) } diff --git a/vector/src/main/java/im/vector/app/features/location/LocationTracker.kt b/vector/src/main/java/im/vector/app/features/location/LocationTracker.kt index 162fbc5959..b7006370a6 100644 --- a/vector/src/main/java/im/vector/app/features/location/LocationTracker.kt +++ b/vector/src/main/java/im/vector/app/features/location/LocationTracker.kt @@ -26,7 +26,9 @@ import androidx.core.location.LocationListenerCompat import im.vector.app.BuildConfig import timber.log.Timber import javax.inject.Inject +import javax.inject.Singleton +@Singleton class LocationTracker @Inject constructor( context: Context ) : LocationListenerCompat { @@ -38,18 +40,17 @@ class LocationTracker @Inject constructor( fun onLocationProviderIsNotAvailable() } - private var callback: Callback? = null + private var callbacks = mutableListOf() private var hasGpsProviderLiveLocation = false @RequiresPermission(anyOf = [Manifest.permission.ACCESS_COARSE_LOCATION, Manifest.permission.ACCESS_FINE_LOCATION]) - fun start(callback: Callback?) { + fun start() { Timber.d("## LocationTracker. start()") hasGpsProviderLiveLocation = false - this.callback = callback if (locationManager == null) { - callback?.onLocationProviderIsNotAvailable() + callbacks.forEach { it.onLocationProviderIsNotAvailable() } Timber.v("## LocationTracker. LocationManager is not available") return } @@ -79,7 +80,7 @@ class LocationTracker @Inject constructor( ) } ?: run { - callback?.onLocationProviderIsNotAvailable() + callbacks.forEach { it.onLocationProviderIsNotAvailable() } Timber.v("## LocationTracker. There is no location provider available") } } @@ -88,7 +89,20 @@ class LocationTracker @Inject constructor( fun stop() { Timber.d("## LocationTracker. stop()") locationManager?.removeUpdates(this) - callback = null + callbacks.clear() + } + + fun addCallback(callback: Callback) { + if (!callbacks.contains(callback)) { + callbacks.add(callback) + } + } + + fun removeCallback(callback: Callback) { + callbacks.remove(callback) + if (callbacks.size == 0) { + stop() + } } override fun onLocationChanged(location: Location) { @@ -113,12 +127,12 @@ class LocationTracker @Inject constructor( } } } - callback?.onLocationUpdate(location.toLocationData()) + callbacks.forEach { it.onLocationUpdate(location.toLocationData()) } } override fun onProviderDisabled(provider: String) { Timber.d("## LocationTracker. onProviderDisabled: $provider") - callback?.onLocationProviderIsNotAvailable() + callbacks.forEach { it.onLocationProviderIsNotAvailable() } } private fun Location.toLocationData(): LocationData { diff --git a/vector/src/main/java/im/vector/app/features/location/live/duration/ChooseLiveDurationBottomSheet.kt b/vector/src/main/java/im/vector/app/features/location/live/duration/ChooseLiveDurationBottomSheet.kt new file mode 100644 index 0000000000..36165d4524 --- /dev/null +++ b/vector/src/main/java/im/vector/app/features/location/live/duration/ChooseLiveDurationBottomSheet.kt @@ -0,0 +1,86 @@ +/* + * Copyright 2019 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package im.vector.app.features.location.live.duration + +import android.os.Bundle +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import dagger.hilt.android.AndroidEntryPoint +import im.vector.app.R +import im.vector.app.core.platform.VectorBaseBottomSheetDialogFragment +import im.vector.app.core.platform.VectorBaseBottomSheetDialogFragment.ResultListener.Companion.RESULT_OK +import im.vector.app.databinding.BottomSheetChooseLiveLocationShareDurationBinding + +/** + * 15 minutes. + */ +private const val DURATION_IN_MS_OPTION_1 = 15 * 60_000L + +/** + * 1 hour. + */ +private const val DURATION_IN_MS_OPTION_2 = 60 * 60_000L + +/** + * 8 hours. + */ +private const val DURATION_IN_MS_OPTION_3 = 8 * 60 * 60_000L + +/** + * Bottom sheet displaying list of options to choose the duration of the location live sharing. + */ +@AndroidEntryPoint +class ChooseLiveDurationBottomSheet : + VectorBaseBottomSheetDialogFragment() { + + override fun getBinding(inflater: LayoutInflater, container: ViewGroup?): BottomSheetChooseLiveLocationShareDurationBinding { + return BottomSheetChooseLiveLocationShareDurationBinding.inflate(inflater, container, false) + } + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + initConfirmButton() + } + + // we are not using state for this one as it's static, so no need to override invalidate() + + private fun initConfirmButton() { + views.liveLocShareChooseDurationConfirm.setOnClickListener { + val currentChoice = getCurrentChoice() + resultListener?.onBottomSheetResult(RESULT_OK, currentChoice) + dismiss() + } + } + + private fun getCurrentChoice(): Long { + return when (views.liveLocShareChooseDurationOptions.checkedRadioButtonId) { + R.id.liveLocShareChooseDurationOption1 -> DURATION_IN_MS_OPTION_1 + R.id.liveLocShareChooseDurationOption2 -> DURATION_IN_MS_OPTION_2 + R.id.liveLocShareChooseDurationOption3 -> DURATION_IN_MS_OPTION_3 + else -> DURATION_IN_MS_OPTION_1 + } + } + + companion object { + fun newInstance(resultListener: ResultListener): ChooseLiveDurationBottomSheet { + val bottomSheet = ChooseLiveDurationBottomSheet() + bottomSheet.resultListener = resultListener + return bottomSheet + } + } +} diff --git a/vector/src/main/java/im/vector/app/features/login/AbstractLoginFragment.kt b/vector/src/main/java/im/vector/app/features/login/AbstractLoginFragment.kt index 8b83873142..f5e48e84e7 100644 --- a/vector/src/main/java/im/vector/app/features/login/AbstractLoginFragment.kt +++ b/vector/src/main/java/im/vector/app/features/login/AbstractLoginFragment.kt @@ -26,7 +26,6 @@ import com.airbnb.mvrx.withState import com.google.android.material.dialog.MaterialAlertDialogBuilder import im.vector.app.R import im.vector.app.core.dialogs.UnrecognizedCertificateDialog -import im.vector.app.core.extensions.exhaustive import im.vector.app.core.platform.OnBackPressed import im.vector.app.core.platform.VectorBaseFragment import kotlinx.coroutines.CancellationException @@ -69,7 +68,7 @@ abstract class AbstractLoginFragment : VectorBaseFragment( else -> // This is handled by the Activity Unit - }.exhaustive + } } override fun showFailure(throwable: Throwable) { diff --git a/vector/src/main/java/im/vector/app/features/login/LoginAction.kt b/vector/src/main/java/im/vector/app/features/login/LoginAction.kt index 70ca49a10e..800df32b9d 100644 --- a/vector/src/main/java/im/vector/app/features/login/LoginAction.kt +++ b/vector/src/main/java/im/vector/app/features/login/LoginAction.kt @@ -20,7 +20,7 @@ import im.vector.app.core.platform.VectorViewModelAction import org.matrix.android.sdk.api.auth.data.Credentials import org.matrix.android.sdk.api.auth.data.SsoIdentityProvider import org.matrix.android.sdk.api.auth.registration.RegisterThreePid -import org.matrix.android.sdk.internal.network.ssl.Fingerprint +import org.matrix.android.sdk.api.network.ssl.Fingerprint sealed class LoginAction : VectorViewModelAction { data class OnGetStarted(val resetLoginConfig: Boolean) : LoginAction() diff --git a/vector/src/main/java/im/vector/app/features/login/LoginActivity.kt b/vector/src/main/java/im/vector/app/features/login/LoginActivity.kt index a40f26acec..79d2d37c44 100644 --- a/vector/src/main/java/im/vector/app/features/login/LoginActivity.kt +++ b/vector/src/main/java/im/vector/app/features/login/LoginActivity.kt @@ -35,7 +35,6 @@ import im.vector.app.R import im.vector.app.core.extensions.POP_BACK_STACK_EXCLUSIVE import im.vector.app.core.extensions.addFragment import im.vector.app.core.extensions.addFragmentToBackstack -import im.vector.app.core.extensions.exhaustive import im.vector.app.core.extensions.validateBackPressed import im.vector.app.core.platform.VectorBaseActivity import im.vector.app.databinding.ActivityLoginBinding @@ -43,10 +42,10 @@ import im.vector.app.features.analytics.plan.MobileScreen import im.vector.app.features.home.HomeActivity import im.vector.app.features.login.terms.LoginTermsFragment import im.vector.app.features.login.terms.LoginTermsFragmentArgument -import im.vector.app.features.login.terms.toLocalizedLoginTerms import im.vector.app.features.pin.UnlockedActivity import org.matrix.android.sdk.api.auth.registration.FlowResult import org.matrix.android.sdk.api.auth.registration.Stage +import org.matrix.android.sdk.api.auth.toLocalizedLoginTerms import org.matrix.android.sdk.api.extensions.tryOrNull /** @@ -197,7 +196,7 @@ open class LoginActivity : VectorBaseActivity(), UnlockedA is LoginViewEvents.Loading -> // This is handled by the Fragments Unit - }.exhaustive + } } private fun updateWithState(loginViewState: LoginViewState) { @@ -260,13 +259,13 @@ open class LoginActivity : VectorBaseActivity(), UnlockedA tag = FRAGMENT_LOGIN_TAG, option = commonOption) LoginMode.Unsupported -> onLoginModeNotSupported(state.loginModeSupportedTypes) - }.exhaustive + } } SignMode.SignInWithMatrixId -> addFragmentToBackstack(views.loginFragmentContainer, LoginFragment::class.java, tag = FRAGMENT_LOGIN_TAG, option = commonOption) - }.exhaustive + } } /** diff --git a/vector/src/main/java/im/vector/app/features/login/LoginCaptchaFragment.kt b/vector/src/main/java/im/vector/app/features/login/LoginCaptchaFragment.kt index fd180c4f97..45b6e5b8cd 100644 --- a/vector/src/main/java/im/vector/app/features/login/LoginCaptchaFragment.kt +++ b/vector/src/main/java/im/vector/app/features/login/LoginCaptchaFragment.kt @@ -37,7 +37,7 @@ import im.vector.app.R import im.vector.app.core.utils.AssetReader import im.vector.app.databinding.FragmentLoginCaptchaBinding import kotlinx.parcelize.Parcelize -import org.matrix.android.sdk.internal.di.MoshiProvider +import org.matrix.android.sdk.api.util.MatrixJsonParser import timber.log.Timber import java.net.URLDecoder import java.util.Formatter @@ -171,7 +171,7 @@ class LoginCaptchaFragment @Inject constructor( try { // URL decode json = URLDecoder.decode(json, "UTF-8") - javascriptResponse = MoshiProvider.providesMoshi().adapter(JavascriptResponse::class.java).fromJson(json) + javascriptResponse = MatrixJsonParser.getMoshi().adapter(JavascriptResponse::class.java).fromJson(json) } catch (e: Exception) { Timber.e(e, "## shouldOverrideUrlLoading(): failed") } diff --git a/vector/src/main/java/im/vector/app/features/login/LoginFragment.kt b/vector/src/main/java/im/vector/app/features/login/LoginFragment.kt index da61d95997..49198087d9 100644 --- a/vector/src/main/java/im/vector/app/features/login/LoginFragment.kt +++ b/vector/src/main/java/im/vector/app/features/login/LoginFragment.kt @@ -28,9 +28,7 @@ import androidx.core.view.isVisible import androidx.lifecycle.lifecycleScope import com.airbnb.mvrx.Fail import com.airbnb.mvrx.Loading -import com.airbnb.mvrx.Success import im.vector.app.R -import im.vector.app.core.extensions.exhaustive import im.vector.app.core.extensions.hideKeyboard import im.vector.app.core.extensions.hidePassword import im.vector.app.core.extensions.toReducedUrl @@ -97,7 +95,7 @@ class LoginFragment @Inject constructor() : AbstractSSOLoginFragment SocialLoginButtonsView.Mode.MODE_SIGN_UP SignMode.SignIn, SignMode.SignInWithMatrixId -> SocialLoginButtonsView.Mode.MODE_SIGN_IN - }.exhaustive + } } private fun submit() { @@ -127,7 +125,7 @@ class LoginFragment @Inject constructor() : AbstractSSOLoginFragment Unit + else -> Unit } when (state.asyncRegistration) { @@ -300,7 +298,7 @@ class LoginFragment @Inject constructor() : AbstractSSOLoginFragment Unit + else -> Unit } } diff --git a/vector/src/main/java/im/vector/app/features/login/LoginMode.kt b/vector/src/main/java/im/vector/app/features/login/LoginMode.kt index 00945968fb..9713e492ec 100644 --- a/vector/src/main/java/im/vector/app/features/login/LoginMode.kt +++ b/vector/src/main/java/im/vector/app/features/login/LoginMode.kt @@ -20,8 +20,8 @@ import android.os.Parcelable import kotlinx.parcelize.Parcelize import org.matrix.android.sdk.api.auth.data.SsoIdentityProvider -sealed class LoginMode : Parcelable -/** because persist state */ { +sealed class LoginMode : Parcelable { // Parcelable because persist state + @Parcelize object Unknown : LoginMode() @Parcelize object Password : LoginMode() @Parcelize data class Sso(val ssoIdentityProviders: List?) : LoginMode() diff --git a/vector/src/main/java/im/vector/app/features/login/LoginResetPasswordFragment.kt b/vector/src/main/java/im/vector/app/features/login/LoginResetPasswordFragment.kt index d121245532..1d32944f9f 100644 --- a/vector/src/main/java/im/vector/app/features/login/LoginResetPasswordFragment.kt +++ b/vector/src/main/java/im/vector/app/features/login/LoginResetPasswordFragment.kt @@ -23,7 +23,6 @@ import android.view.ViewGroup import androidx.lifecycle.lifecycleScope import com.airbnb.mvrx.Fail import com.airbnb.mvrx.Loading -import com.airbnb.mvrx.Success import com.google.android.material.dialog.MaterialAlertDialogBuilder import im.vector.app.R import im.vector.app.core.extensions.hideKeyboard @@ -129,7 +128,7 @@ class LoginResetPasswordFragment @Inject constructor() : AbstractLoginFragment { views.resetPasswordEmailTil.error = errorFormatter.toHumanReadable(state.asyncResetPassword.error) } - is Success -> Unit + else -> Unit } } } diff --git a/vector/src/main/java/im/vector/app/features/login/LoginResetPasswordMailConfirmationFragment.kt b/vector/src/main/java/im/vector/app/features/login/LoginResetPasswordMailConfirmationFragment.kt index 5f376700f8..232e7ab622 100644 --- a/vector/src/main/java/im/vector/app/features/login/LoginResetPasswordMailConfirmationFragment.kt +++ b/vector/src/main/java/im/vector/app/features/login/LoginResetPasswordMailConfirmationFragment.kt @@ -21,7 +21,6 @@ import android.view.LayoutInflater import android.view.View import android.view.ViewGroup import com.airbnb.mvrx.Fail -import com.airbnb.mvrx.Success import com.google.android.material.dialog.MaterialAlertDialogBuilder import im.vector.app.R import im.vector.app.databinding.FragmentLoginResetPasswordMailConfirmationBinding @@ -59,7 +58,7 @@ class LoginResetPasswordMailConfirmationFragment @Inject constructor() : Abstrac setupUi(state) when (state.asyncResetMailConfirmed) { - is Fail -> { + is Fail -> { // Link in email not yet clicked ? val message = if (state.asyncResetMailConfirmed.error.is401()) { getString(R.string.auth_reset_password_error_unauthorized) @@ -73,7 +72,7 @@ class LoginResetPasswordMailConfirmationFragment @Inject constructor() : Abstrac .setPositiveButton(R.string.ok, null) .show() } - is Success -> Unit + else -> Unit } } } diff --git a/vector/src/main/java/im/vector/app/features/login/LoginViewModel.kt b/vector/src/main/java/im/vector/app/features/login/LoginViewModel.kt index bfa924c155..73f5c064e7 100644 --- a/vector/src/main/java/im/vector/app/features/login/LoginViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/login/LoginViewModel.kt @@ -31,7 +31,6 @@ import im.vector.app.core.di.ActiveSessionHolder import im.vector.app.core.di.MavericksAssistedViewModelFactory import im.vector.app.core.di.hiltMavericksViewModelFactory import im.vector.app.core.extensions.configureAndStart -import im.vector.app.core.extensions.exhaustive import im.vector.app.core.platform.VectorViewModel import im.vector.app.core.resources.StringProvider import im.vector.app.core.utils.ensureTrailingSlash @@ -131,7 +130,7 @@ class LoginViewModel @AssistedInject constructor( is LoginAction.UserAcceptCertificate -> handleUserAcceptCertificate(action) LoginAction.ClearHomeServerHistory -> handleClearHomeServerHistory() is LoginAction.PostViewEvent -> _viewEvents.post(action.viewEvent) - }.exhaustive + } } private fun handleOnGetStarted(action: LoginAction.OnGetStarted) { @@ -173,6 +172,7 @@ class LoginViewModel @AssistedInject constructor( .withAllowedFingerPrints(listOf(action.fingerprint)) .build() ) + else -> Unit } } @@ -275,7 +275,7 @@ class LoginViewModel @AssistedInject constructor( code = MatrixError.FORBIDDEN, message = "Registration is disabled" ), 403)) - */ + */ } catch (failure: Throwable) { if (failure !is CancellationException) { _viewEvents.post(LoginViewEvents.Failure(failure)) @@ -447,7 +447,7 @@ class LoginViewModel @AssistedInject constructor( handle(LoginAction.UpdateHomeServer(matrixOrgUrl)) ServerType.EMS, ServerType.Other -> _viewEvents.post(LoginViewEvents.OnServerSelectionDone(action.serverType)) - }.exhaustive + } } private fun handleInitWith(action: LoginAction.InitWith) { @@ -555,7 +555,7 @@ class LoginViewModel @AssistedInject constructor( SignMode.SignIn -> handleLogin(action) SignMode.SignUp -> handleRegisterWith(action) SignMode.SignInWithMatrixId -> handleDirectLogin(action, null) - }.exhaustive + } } private fun handleDirectLogin(action: LoginAction.LoginOrRegister, homeServerConnectionConfig: HomeServerConnectionConfig?) { @@ -585,7 +585,7 @@ class LoginViewModel @AssistedInject constructor( else -> { onWellKnownError() } - }.exhaustive + } } } diff --git a/vector/src/main/java/im/vector/app/features/login/LoginWebFragment.kt b/vector/src/main/java/im/vector/app/features/login/LoginWebFragment.kt index bb73ed79b2..b55e393339 100644 --- a/vector/src/main/java/im/vector/app/features/login/LoginWebFragment.kt +++ b/vector/src/main/java/im/vector/app/features/login/LoginWebFragment.kt @@ -38,7 +38,7 @@ import im.vector.app.databinding.FragmentLoginWebBinding import im.vector.app.features.signout.soft.SoftLogoutAction import im.vector.app.features.signout.soft.SoftLogoutViewModel import org.matrix.android.sdk.api.auth.data.Credentials -import org.matrix.android.sdk.internal.di.MoshiProvider +import org.matrix.android.sdk.api.util.MatrixJsonParser import timber.log.Timber import java.net.URLDecoder import javax.inject.Inject @@ -203,7 +203,7 @@ class LoginWebFragment @Inject constructor( try { // URL decode json = URLDecoder.decode(json, "UTF-8") - val adapter = MoshiProvider.providesMoshi().adapter(JavascriptResponse::class.java) + val adapter = MatrixJsonParser.getMoshi().adapter(JavascriptResponse::class.java) javascriptResponse = adapter.fromJson(json) } catch (e: Exception) { Timber.e(e, "## shouldOverrideUrlLoading() : fromJson failed") diff --git a/vector/src/main/java/im/vector/app/features/login/SocialLoginButtonsView.kt b/vector/src/main/java/im/vector/app/features/login/SocialLoginButtonsView.kt index f40cad9ec5..68fc2d1c59 100644 --- a/vector/src/main/java/im/vector/app/features/login/SocialLoginButtonsView.kt +++ b/vector/src/main/java/im/vector/app/features/login/SocialLoginButtonsView.kt @@ -28,9 +28,9 @@ import im.vector.app.R import org.matrix.android.sdk.api.auth.data.SsoIdentityProvider class SocialLoginButtonsView @JvmOverloads constructor(context: Context, attrs: AttributeSet? = null, defStyle: Int = 0) : - LinearLayout(context, attrs, defStyle) { + LinearLayout(context, attrs, defStyle) { - interface InteractionListener { + fun interface InteractionListener { fun onProviderSelected(id: String?) } @@ -99,10 +99,10 @@ class SocialLoginButtonsView @JvmOverloads constructor(context: Context, attrs: SsoIdentityProvider.BRAND_TWITTER -> { MaterialButton(context, null, R.attr.vctr_social_login_button_twitter_style) } - SsoIdentityProvider.BRAND_GITLAB -> { + SsoIdentityProvider.BRAND_GITLAB -> { MaterialButton(context, null, R.attr.vctr_social_login_button_gitlab_style) } - else -> { + else -> { // TODO Use iconUrl MaterialButton(context, null, R.attr.materialButtonOutlinedStyle).apply { transformationMethod = null diff --git a/vector/src/main/java/im/vector/app/features/login/terms/LocalizedFlowDataLoginTermsChecked.kt b/vector/src/main/java/im/vector/app/features/login/terms/LocalizedFlowDataLoginTermsChecked.kt index e6b082ceb7..a248b3471b 100644 --- a/vector/src/main/java/im/vector/app/features/login/terms/LocalizedFlowDataLoginTermsChecked.kt +++ b/vector/src/main/java/im/vector/app/features/login/terms/LocalizedFlowDataLoginTermsChecked.kt @@ -16,7 +16,7 @@ package im.vector.app.features.login.terms -import org.matrix.android.sdk.internal.auth.registration.LocalizedFlowDataLoginTerms +import org.matrix.android.sdk.api.auth.data.LocalizedFlowDataLoginTerms data class LocalizedFlowDataLoginTermsChecked(val localizedFlowDataLoginTerms: LocalizedFlowDataLoginTerms, var checked: Boolean = false) diff --git a/vector/src/main/java/im/vector/app/features/login/terms/LoginTermsFragment.kt b/vector/src/main/java/im/vector/app/features/login/terms/LoginTermsFragment.kt index 43b148dcab..262b79226e 100755 --- a/vector/src/main/java/im/vector/app/features/login/terms/LoginTermsFragment.kt +++ b/vector/src/main/java/im/vector/app/features/login/terms/LoginTermsFragment.kt @@ -31,7 +31,7 @@ import im.vector.app.features.login.AbstractLoginFragment import im.vector.app.features.login.LoginAction import im.vector.app.features.login.LoginViewState import kotlinx.parcelize.Parcelize -import org.matrix.android.sdk.internal.auth.registration.LocalizedFlowDataLoginTerms +import org.matrix.android.sdk.api.auth.data.LocalizedFlowDataLoginTerms import javax.inject.Inject @Parcelize diff --git a/vector/src/main/java/im/vector/app/features/login/terms/LoginTermsViewState.kt b/vector/src/main/java/im/vector/app/features/login/terms/LoginTermsViewState.kt index 3641b443e3..3a1dd19a09 100644 --- a/vector/src/main/java/im/vector/app/features/login/terms/LoginTermsViewState.kt +++ b/vector/src/main/java/im/vector/app/features/login/terms/LoginTermsViewState.kt @@ -17,7 +17,7 @@ package im.vector.app.features.login.terms import com.airbnb.mvrx.MavericksState -import org.matrix.android.sdk.internal.auth.registration.LocalizedFlowDataLoginTerms +import org.matrix.android.sdk.api.auth.data.LocalizedFlowDataLoginTerms data class LoginTermsViewState( val localizedFlowDataLoginTermsChecked: List diff --git a/vector/src/main/java/im/vector/app/features/login/terms/PolicyController.kt b/vector/src/main/java/im/vector/app/features/login/terms/PolicyController.kt index 9f0086fb3a..42c39efdac 100644 --- a/vector/src/main/java/im/vector/app/features/login/terms/PolicyController.kt +++ b/vector/src/main/java/im/vector/app/features/login/terms/PolicyController.kt @@ -17,13 +17,14 @@ package im.vector.app.features.login.terms import com.airbnb.epoxy.TypedEpoxyController -import org.matrix.android.sdk.internal.auth.registration.LocalizedFlowDataLoginTerms +import org.matrix.android.sdk.api.auth.data.LocalizedFlowDataLoginTerms import javax.inject.Inject class PolicyController @Inject constructor() : TypedEpoxyController>() { var listener: PolicyControllerListener? = null + var horizontalPadding: Int? = null var homeServer: String? = null override fun buildModels(data: List) { @@ -32,6 +33,7 @@ class PolicyController @Inject constructor() : TypedEpoxyController() { @@ -38,6 +39,9 @@ abstract class PolicyItem : EpoxyModelWithHolder() { @EpoxyAttribute var subtitle: String? = null + @EpoxyAttribute + var horizontalPadding: Int? = null + @EpoxyAttribute(EpoxyAttribute.Option.DoNotHash) var checkChangeListener: CompoundButton.OnCheckedChangeListener? = null @@ -46,13 +50,12 @@ abstract class PolicyItem : EpoxyModelWithHolder() { override fun bind(holder: Holder) { super.bind(holder) - holder.let { - it.checkbox.isChecked = checked - it.checkbox.setOnCheckedChangeListener(checkChangeListener) - it.title.text = title - it.subtitle.text = subtitle - it.view.onClick(clickListener) - } + horizontalPadding?.let { holder.view.setHorizontalPadding(it) } + holder.checkbox.isChecked = checked + holder.checkbox.setOnCheckedChangeListener(checkChangeListener) + holder.title.text = title + holder.subtitle.text = subtitle + holder.view.onClick(clickListener) } // Ensure checkbox behaves as expected (remove the listener) diff --git a/vector/src/main/java/im/vector/app/features/login2/AbstractLoginFragment2.kt b/vector/src/main/java/im/vector/app/features/login2/AbstractLoginFragment2.kt index 8c9749d91e..68568d1420 100644 --- a/vector/src/main/java/im/vector/app/features/login2/AbstractLoginFragment2.kt +++ b/vector/src/main/java/im/vector/app/features/login2/AbstractLoginFragment2.kt @@ -26,7 +26,6 @@ import com.airbnb.mvrx.withState import com.google.android.material.dialog.MaterialAlertDialogBuilder import im.vector.app.R import im.vector.app.core.dialogs.UnrecognizedCertificateDialog -import im.vector.app.core.extensions.exhaustive import im.vector.app.core.platform.OnBackPressed import im.vector.app.core.platform.VectorBaseFragment import kotlinx.coroutines.CancellationException @@ -67,7 +66,7 @@ abstract class AbstractLoginFragment2 : VectorBaseFragment else -> // This is handled by the Activity Unit - }.exhaustive + } } override fun showFailure(throwable: Throwable) { diff --git a/vector/src/main/java/im/vector/app/features/login2/LoginAction2.kt b/vector/src/main/java/im/vector/app/features/login2/LoginAction2.kt index 62179fbc32..85e2e0cea3 100644 --- a/vector/src/main/java/im/vector/app/features/login2/LoginAction2.kt +++ b/vector/src/main/java/im/vector/app/features/login2/LoginAction2.kt @@ -21,7 +21,7 @@ import im.vector.app.features.login.LoginConfig import org.matrix.android.sdk.api.auth.data.Credentials import org.matrix.android.sdk.api.auth.data.SsoIdentityProvider import org.matrix.android.sdk.api.auth.registration.RegisterThreePid -import org.matrix.android.sdk.internal.network.ssl.Fingerprint +import org.matrix.android.sdk.api.network.ssl.Fingerprint sealed class LoginAction2 : VectorViewModelAction { // First action diff --git a/vector/src/main/java/im/vector/app/features/login2/LoginCaptchaFragment2.kt b/vector/src/main/java/im/vector/app/features/login2/LoginCaptchaFragment2.kt index 222949bcbd..f729584f51 100644 --- a/vector/src/main/java/im/vector/app/features/login2/LoginCaptchaFragment2.kt +++ b/vector/src/main/java/im/vector/app/features/login2/LoginCaptchaFragment2.kt @@ -37,7 +37,7 @@ import im.vector.app.core.utils.AssetReader import im.vector.app.databinding.FragmentLoginCaptchaBinding import im.vector.app.features.login.JavascriptResponse import im.vector.app.features.login.LoginCaptchaFragmentArgument -import org.matrix.android.sdk.internal.di.MoshiProvider +import org.matrix.android.sdk.api.util.MatrixJsonParser import timber.log.Timber import java.net.URLDecoder import java.util.Formatter @@ -166,7 +166,7 @@ class LoginCaptchaFragment2 @Inject constructor( try { // URL decode json = URLDecoder.decode(json, "UTF-8") - javascriptResponse = MoshiProvider.providesMoshi().adapter(JavascriptResponse::class.java).fromJson(json) + javascriptResponse = MatrixJsonParser.getMoshi().adapter(JavascriptResponse::class.java).fromJson(json) } catch (e: Exception) { Timber.e(e, "## shouldOverrideUrlLoading(): failed") } diff --git a/vector/src/main/java/im/vector/app/features/login2/LoginGenericTextInputFormFragment2.kt b/vector/src/main/java/im/vector/app/features/login2/LoginGenericTextInputFormFragment2.kt index 76af86fda8..f623fb5abd 100644 --- a/vector/src/main/java/im/vector/app/features/login2/LoginGenericTextInputFormFragment2.kt +++ b/vector/src/main/java/im/vector/app/features/login2/LoginGenericTextInputFormFragment2.kt @@ -75,8 +75,8 @@ class LoginGenericTextInputFormFragment2 @Inject constructor() : AbstractLoginFr if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { views.loginGenericTextInputFormTextInput.setAutofillHints( when (params.mode) { - TextInputFormFragmentMode.SetEmail -> HintConstants.AUTOFILL_HINT_EMAIL_ADDRESS - TextInputFormFragmentMode.SetMsisdn -> HintConstants.AUTOFILL_HINT_PHONE_NUMBER + TextInputFormFragmentMode.SetEmail -> HintConstants.AUTOFILL_HINT_EMAIL_ADDRESS + TextInputFormFragmentMode.SetMsisdn -> HintConstants.AUTOFILL_HINT_PHONE_NUMBER TextInputFormFragmentMode.ConfirmMsisdn -> HintConstants.AUTOFILL_HINT_SMS_OTP } ) diff --git a/vector/src/main/java/im/vector/app/features/login2/LoginViewModel2.kt b/vector/src/main/java/im/vector/app/features/login2/LoginViewModel2.kt index b73988126b..62f0007104 100644 --- a/vector/src/main/java/im/vector/app/features/login2/LoginViewModel2.kt +++ b/vector/src/main/java/im/vector/app/features/login2/LoginViewModel2.kt @@ -18,7 +18,6 @@ package im.vector.app.features.login2 import android.content.Context import android.net.Uri -import androidx.lifecycle.viewModelScope import com.airbnb.mvrx.Loading import com.airbnb.mvrx.MavericksViewModelFactory import dagger.assisted.Assisted @@ -29,7 +28,6 @@ import im.vector.app.core.di.ActiveSessionHolder import im.vector.app.core.di.MavericksAssistedViewModelFactory import im.vector.app.core.di.hiltMavericksViewModelFactory import im.vector.app.core.extensions.configureAndStart -import im.vector.app.core.extensions.exhaustive import im.vector.app.core.extensions.tryAsync import im.vector.app.core.platform.VectorViewModel import im.vector.app.core.resources.StringProvider @@ -137,7 +135,7 @@ class LoginViewModel2 @AssistedInject constructor( LoginAction2.ClearHomeServerHistory -> handleClearHomeServerHistory() is LoginAction2.PostViewEvent -> _viewEvents.post(action.viewEvent) is LoginAction2.Finish -> handleFinish() - }.exhaustive + } } private fun handleFinish() { @@ -172,6 +170,7 @@ class LoginViewModel2 @AssistedInject constructor( handleSetUserPassword(finalLastAction) is LoginAction2.LoginWith -> handleLoginWith(finalLastAction) + else -> Unit } } @@ -500,7 +499,7 @@ class LoginViewModel2 @AssistedInject constructor( SignMode2.Unknown -> error("Developer error, invalid sign mode") SignMode2.SignIn -> handleSetUserNameForSignIn(action, null) SignMode2.SignUp -> handleSetUserNameForSignUp(action) - }.exhaustive + } } private fun handleSetUserPassword(action: LoginAction2.SetUserPassword) = withState { state -> @@ -508,7 +507,7 @@ class LoginViewModel2 @AssistedInject constructor( SignMode2.Unknown -> error("Developer error, invalid sign mode") SignMode2.SignIn -> handleSignInWithPassword(action) SignMode2.SignUp -> handleRegisterWithPassword(action) - }.exhaustive + } } private fun handleRegisterWithPassword(action: LoginAction2.SetUserPassword) = withState { state -> @@ -588,7 +587,7 @@ class LoginViewModel2 @AssistedInject constructor( else -> { onWellKnownError() } - }.exhaustive + } } } @@ -772,7 +771,7 @@ class LoginViewModel2 @AssistedInject constructor( ), httpCode = 403 ) - */ + */ LoginViewEvents2.OpenSignUpChooseUsernameScreen } catch (throwable: Throwable) { diff --git a/vector/src/main/java/im/vector/app/features/login2/LoginWebFragment2.kt b/vector/src/main/java/im/vector/app/features/login2/LoginWebFragment2.kt index 4427f08309..789cd459ac 100644 --- a/vector/src/main/java/im/vector/app/features/login2/LoginWebFragment2.kt +++ b/vector/src/main/java/im/vector/app/features/login2/LoginWebFragment2.kt @@ -39,7 +39,7 @@ import im.vector.app.features.login.JavascriptResponse import im.vector.app.features.signout.soft.SoftLogoutAction import im.vector.app.features.signout.soft.SoftLogoutViewModel import org.matrix.android.sdk.api.auth.data.Credentials -import org.matrix.android.sdk.internal.di.MoshiProvider +import org.matrix.android.sdk.api.util.MatrixJsonParser import timber.log.Timber import java.net.URLDecoder import javax.inject.Inject @@ -204,7 +204,7 @@ class LoginWebFragment2 @Inject constructor( try { // URL decode json = URLDecoder.decode(json, "UTF-8") - val adapter = MoshiProvider.providesMoshi().adapter(JavascriptResponse::class.java) + val adapter = MatrixJsonParser.getMoshi().adapter(JavascriptResponse::class.java) javascriptResponse = adapter.fromJson(json) } catch (e: Exception) { Timber.e(e, "## shouldOverrideUrlLoading() : fromJson failed") diff --git a/vector/src/main/java/im/vector/app/features/login2/created/AccountCreatedViewModel.kt b/vector/src/main/java/im/vector/app/features/login2/created/AccountCreatedViewModel.kt index 568cdab119..ac4edba7fb 100644 --- a/vector/src/main/java/im/vector/app/features/login2/created/AccountCreatedViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/login2/created/AccountCreatedViewModel.kt @@ -81,7 +81,7 @@ class AccountCreatedViewModel @AssistedInject constructor( private fun handleSetAvatar(action: AccountCreatedAction.SetAvatar) { setState { copy(isLoading = true) } viewModelScope.launch { - val result = runCatching { session.updateAvatar(session.myUserId, action.avatarUri, action.filename) } + val result = runCatching { session.profileService().updateAvatar(session.myUserId, action.avatarUri, action.filename) } .onFailure { _viewEvents.post(AccountCreatedViewEvents.Failure(it)) } setState { copy( @@ -95,7 +95,7 @@ class AccountCreatedViewModel @AssistedInject constructor( private fun handleSetDisplayName(action: AccountCreatedAction.SetDisplayName) { setState { copy(isLoading = true) } viewModelScope.launch { - val result = runCatching { session.setDisplayName(session.myUserId, action.displayName) } + val result = runCatching { session.profileService().setDisplayName(session.myUserId, action.displayName) } .onFailure { _viewEvents.post(AccountCreatedViewEvents.Failure(it)) } setState { copy( diff --git a/vector/src/main/java/im/vector/app/features/login2/terms/LoginTermsFragment2.kt b/vector/src/main/java/im/vector/app/features/login2/terms/LoginTermsFragment2.kt index 0be696e1c8..303fc5ef17 100755 --- a/vector/src/main/java/im/vector/app/features/login2/terms/LoginTermsFragment2.kt +++ b/vector/src/main/java/im/vector/app/features/login2/terms/LoginTermsFragment2.kt @@ -33,7 +33,7 @@ import im.vector.app.features.login.terms.PolicyController import im.vector.app.features.login2.AbstractLoginFragment2 import im.vector.app.features.login2.LoginAction2 import im.vector.app.features.login2.LoginViewState2 -import org.matrix.android.sdk.internal.auth.registration.LocalizedFlowDataLoginTerms +import org.matrix.android.sdk.api.auth.data.LocalizedFlowDataLoginTerms import javax.inject.Inject /** diff --git a/vector/src/main/java/im/vector/app/features/matrixto/MatrixToAction.kt b/vector/src/main/java/im/vector/app/features/matrixto/MatrixToAction.kt index f38049640d..25db811600 100644 --- a/vector/src/main/java/im/vector/app/features/matrixto/MatrixToAction.kt +++ b/vector/src/main/java/im/vector/app/features/matrixto/MatrixToAction.kt @@ -24,7 +24,7 @@ sealed class MatrixToAction : VectorViewModelAction { object FailedToResolveUser : MatrixToAction() object FailedToStartChatting : MatrixToAction() data class JoinSpace(val spaceID: String, val viaServers: List?) : MatrixToAction() - data class JoinRoom(val roomId: String, val viaServers: List?) : MatrixToAction() + data class JoinRoom(val roomIdOrAlias: String, val viaServers: List?) : MatrixToAction() data class OpenSpace(val spaceID: String) : MatrixToAction() data class OpenRoom(val roomId: String) : MatrixToAction() } diff --git a/vector/src/main/java/im/vector/app/features/matrixto/MatrixToBottomSheet.kt b/vector/src/main/java/im/vector/app/features/matrixto/MatrixToBottomSheet.kt index 63e0398fc1..61dcd48779 100644 --- a/vector/src/main/java/im/vector/app/features/matrixto/MatrixToBottomSheet.kt +++ b/vector/src/main/java/im/vector/app/features/matrixto/MatrixToBottomSheet.kt @@ -65,18 +65,17 @@ class MatrixToBottomSheet : override fun invalidate() = withState(viewModel) { state -> super.invalidate() when (state.linkType) { - is PermalinkData.RoomLink -> { + is PermalinkData.RoomLink -> { views.matrixToCardContentLoading.isVisible = state.roomPeekResult is Incomplete showFragment(MatrixToRoomSpaceFragment::class, Bundle()) } - is PermalinkData.UserLink -> { + is PermalinkData.UserLink -> { views.matrixToCardContentLoading.isVisible = state.matrixItem is Incomplete showFragment(MatrixToUserFragment::class, Bundle()) } - is PermalinkData.GroupLink -> { - } - is PermalinkData.FallbackLink -> { - } + is PermalinkData.GroupLink -> Unit + is PermalinkData.FallbackLink -> Unit + is PermalinkData.RoomEmailInviteLink -> Unit } } diff --git a/vector/src/main/java/im/vector/app/features/matrixto/MatrixToBottomSheetViewModel.kt b/vector/src/main/java/im/vector/app/features/matrixto/MatrixToBottomSheetViewModel.kt index e741f6fb39..b6db5f487d 100644 --- a/vector/src/main/java/im/vector/app/features/matrixto/MatrixToBottomSheetViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/matrixto/MatrixToBottomSheetViewModel.kt @@ -28,14 +28,15 @@ import im.vector.app.R import im.vector.app.core.di.MavericksAssistedViewModelFactory import im.vector.app.core.di.hiltMavericksViewModelFactory import im.vector.app.core.error.ErrorFormatter -import im.vector.app.core.extensions.exhaustive import im.vector.app.core.platform.VectorViewModel import im.vector.app.core.resources.StringProvider import im.vector.app.features.createdirect.DirectRoomHelper import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.launch +import org.matrix.android.sdk.api.MatrixPatterns import org.matrix.android.sdk.api.extensions.tryOrNull import org.matrix.android.sdk.api.session.Session +import org.matrix.android.sdk.api.session.getRoom import org.matrix.android.sdk.api.session.permalinks.PermalinkData import org.matrix.android.sdk.api.session.room.model.Membership import org.matrix.android.sdk.api.session.room.peeking.PeekResult @@ -49,8 +50,8 @@ class MatrixToBottomSheetViewModel @AssistedInject constructor( private val session: Session, private val stringProvider: StringProvider, private val directRoomHelper: DirectRoomHelper, - private val errorFormatter: ErrorFormatter) : - VectorViewModel(initialState) { + private val errorFormatter: ErrorFormatter +) : VectorViewModel(initialState) { @AssistedFactory interface Factory : MavericksAssistedViewModelFactory { @@ -61,22 +62,23 @@ class MatrixToBottomSheetViewModel @AssistedInject constructor( init { when (initialState.linkType) { - is PermalinkData.RoomLink -> { + is PermalinkData.RoomLink -> { setState { copy(roomPeekResult = Loading()) } } - is PermalinkData.UserLink -> { + is PermalinkData.UserLink -> { setState { copy(matrixItem = Loading()) } } - is PermalinkData.GroupLink -> { + is PermalinkData.GroupLink -> { // Not yet supported } - is PermalinkData.FallbackLink -> { + is PermalinkData.FallbackLink -> { // Not yet supported } + is PermalinkData.RoomEmailInviteLink -> Unit } viewModelScope.launch(Dispatchers.IO) { resolveLink(initialState) @@ -109,7 +111,7 @@ class MatrixToBottomSheetViewModel @AssistedInject constructor( // could this room be already known val knownRoom = if (permalinkData.isRoomAlias) { tryOrNull { - session.getRoomIdByAlias(permalinkData.roomIdOrAlias, false) + session.roomService().getRoomIdByAlias(permalinkData.roomIdOrAlias, false) } ?.getOrNull() ?.roomId?.let { @@ -194,7 +196,7 @@ class MatrixToBottomSheetViewModel @AssistedInject constructor( private fun checkForKnownMembers(someMembers: List) { viewModelScope.launch(Dispatchers.Default) { val knownMembers = someMembers.filter { - session.getExistingDirectRoomWithUser(it.id) != null + session.roomService().getExistingDirectRoomWithUser(it.id) != null } // put one with avatar first, and take 5 val finalRes = (knownMembers.filter { it.avatarUrl != null } + knownMembers.filter { it.avatarUrl == null }) @@ -233,7 +235,7 @@ class MatrixToBottomSheetViewModel @AssistedInject constructor( } private suspend fun resolveUser(userId: String): User { - return tryOrNull { session.resolveUser(userId) } + return tryOrNull { session.userService().resolveUser(userId) } // Create raw user in case the user is not searchable ?: User(userId, null, null) } @@ -243,7 +245,7 @@ class MatrixToBottomSheetViewModel @AssistedInject constructor( * main thing is trying to see if it's a space or a room */ private suspend fun resolveRoom(roomIdOrAlias: String): PeekResult { - return session.peekRoom(roomIdOrAlias) + return session.roomService().peekRoom(roomIdOrAlias) } override fun handle(action: MatrixToAction) { @@ -263,7 +265,7 @@ class MatrixToBottomSheetViewModel @AssistedInject constructor( is MatrixToAction.OpenRoom -> { _viewEvents.post(MatrixToViewEvents.NavigateToRoom(action.roomId)) } - }.exhaustive + } } private fun handleJoinSpace(joinSpace: MatrixToAction.JoinSpace) { @@ -296,8 +298,12 @@ class MatrixToBottomSheetViewModel @AssistedInject constructor( } viewModelScope.launch { try { - session.joinRoom(action.roomId, null, action.viaServers?.take(3) ?: emptyList()) - _viewEvents.post(MatrixToViewEvents.NavigateToRoom(action.roomId)) + session.roomService().joinRoom( + roomIdOrAlias = action.roomIdOrAlias, + reason = null, + viaServers = action.viaServers?.take(3) ?: emptyList() + ) + _viewEvents.post(MatrixToViewEvents.NavigateToRoom(getRoomIdFromRoomIdOrAlias(action.roomIdOrAlias))) } catch (failure: Throwable) { _viewEvents.post(MatrixToViewEvents.ShowModalError(errorFormatter.toHumanReadable(failure))) } finally { @@ -309,6 +315,12 @@ class MatrixToBottomSheetViewModel @AssistedInject constructor( } } + private suspend fun getRoomIdFromRoomIdOrAlias(roomIdOrAlias: String): String { + return if (MatrixPatterns.isRoomAlias(roomIdOrAlias)) { + session.roomService().getRoomIdByAlias(roomIdOrAlias, true).get().roomId + } else roomIdOrAlias + } + private fun handleStartChatting(action: MatrixToAction.StartChattingWithUser) { setState { copy(startChattingState = Loading()) diff --git a/vector/src/main/java/im/vector/app/features/matrixto/MatrixToRoomSpaceFragment.kt b/vector/src/main/java/im/vector/app/features/matrixto/MatrixToRoomSpaceFragment.kt index 3b0fc175b0..4e3159b5b3 100644 --- a/vector/src/main/java/im/vector/app/features/matrixto/MatrixToRoomSpaceFragment.kt +++ b/vector/src/main/java/im/vector/app/features/matrixto/MatrixToRoomSpaceFragment.kt @@ -60,10 +60,10 @@ class MatrixToRoomSpaceFragment @Inject constructor( Uninitialized -> { views.matrixToCardContentVisibility.isVisible = false } - is Loading -> { + is Loading -> { views.matrixToCardContentVisibility.isVisible = false } - is Success -> { + is Success -> { views.matrixToCardContentVisibility.isVisible = true when (val peek = item.invoke()) { is RoomInfoResult.FullInfo -> { @@ -154,7 +154,7 @@ class MatrixToRoomSpaceFragment @Inject constructor( } } } - is Fail -> { + is Fail -> { // TODO display some error copy? sharedViewModel.handle(MatrixToAction.FailedToResolveUser) } @@ -176,14 +176,14 @@ class MatrixToRoomSpaceFragment @Inject constructor( Uninitialized -> { views.matrixToCardMainButton.render(ButtonStateView.State.Button) } - is Success -> { + is Success -> { views.matrixToCardMainButton.render(ButtonStateView.State.Button) } - is Fail -> { + is Fail -> { views.matrixToCardMainButton.render(ButtonStateView.State.Error) // TODO display some error copy? } - is Loading -> { + is Loading -> { views.matrixToCardMainButton.render(ButtonStateView.State.Loading) } } @@ -191,7 +191,7 @@ class MatrixToRoomSpaceFragment @Inject constructor( private fun mainButtonClicked() = withState(sharedViewModel) { state -> when (val info = state.roomPeekResult.invoke()) { - is RoomInfoResult.FullInfo -> { + is RoomInfoResult.FullInfo -> { when (info.membership) { Membership.NONE, Membership.INVITE, diff --git a/vector/src/main/java/im/vector/app/features/matrixto/MatrixToUserFragment.kt b/vector/src/main/java/im/vector/app/features/matrixto/MatrixToUserFragment.kt index 3792183bca..b0d68c57e8 100644 --- a/vector/src/main/java/im/vector/app/features/matrixto/MatrixToUserFragment.kt +++ b/vector/src/main/java/im/vector/app/features/matrixto/MatrixToUserFragment.kt @@ -60,16 +60,16 @@ class MatrixToUserFragment @Inject constructor( Uninitialized -> { views.matrixToCardUserContentVisibility.isVisible = false } - is Loading -> { + is Loading -> { views.matrixToCardUserContentVisibility.isVisible = false } - is Success -> { + is Success -> { views.matrixToCardUserContentVisibility.isVisible = true views.matrixToCardNameText.setTextOrHide(item.invoke().displayName) views.matrixToCardUserIdText.setTextOrHide(item.invoke().id) avatarRenderer.render(item.invoke(), views.matrixToCardAvatar) } - is Fail -> { + is Fail -> { // TODO display some error copy? sharedViewModel.handle(MatrixToAction.FailedToResolveUser) } @@ -80,17 +80,17 @@ class MatrixToUserFragment @Inject constructor( views.matrixToCardButtonLoading.isVisible = false views.matrixToCardSendMessageButton.isVisible = false } - is Success -> { + is Success -> { views.matrixToCardButtonLoading.isVisible = false views.matrixToCardSendMessageButton.isVisible = true } - is Fail -> { + is Fail -> { views.matrixToCardButtonLoading.isVisible = false views.matrixToCardSendMessageButton.isVisible = true // TODO display some error copy? sharedViewModel.handle(MatrixToAction.FailedToStartChatting) } - is Loading -> { + is Loading -> { views.matrixToCardButtonLoading.isVisible = true views.matrixToCardSendMessageButton.isInvisible = true } diff --git a/vector/src/main/java/im/vector/app/features/media/ImageContentRenderer.kt b/vector/src/main/java/im/vector/app/features/media/ImageContentRenderer.kt index 65c99362b9..50325327db 100644 --- a/vector/src/main/java/im/vector/app/features/media/ImageContentRenderer.kt +++ b/vector/src/main/java/im/vector/app/features/media/ImageContentRenderer.kt @@ -44,8 +44,8 @@ import im.vector.app.core.utils.DimensionConverter import kotlinx.parcelize.Parcelize import org.matrix.android.sdk.api.extensions.tryOrNull import org.matrix.android.sdk.api.session.content.ContentUrlResolver +import org.matrix.android.sdk.api.session.crypto.attachments.ElementToDecrypt import org.matrix.android.sdk.api.session.media.PreviewUrlData -import org.matrix.android.sdk.internal.crypto.attachments.ElementToDecrypt import timber.log.Timber import java.io.File import javax.inject.Inject diff --git a/vector/src/main/java/im/vector/app/features/media/RoomEventsAttachmentProvider.kt b/vector/src/main/java/im/vector/app/features/media/RoomEventsAttachmentProvider.kt index 1e0a3a2ad9..cd868c9f2f 100644 --- a/vector/src/main/java/im/vector/app/features/media/RoomEventsAttachmentProvider.kt +++ b/vector/src/main/java/im/vector/app/features/media/RoomEventsAttachmentProvider.kt @@ -21,17 +21,18 @@ import im.vector.app.core.resources.StringProvider import im.vector.lib.attachmentviewer.AttachmentInfo import kotlinx.coroutines.CoroutineScope import org.matrix.android.sdk.api.extensions.tryOrNull +import org.matrix.android.sdk.api.session.crypto.attachments.toElementToDecrypt import org.matrix.android.sdk.api.session.events.model.toModel import org.matrix.android.sdk.api.session.file.FileService import org.matrix.android.sdk.api.session.room.model.message.MessageContent import org.matrix.android.sdk.api.session.room.model.message.MessageImageContent +import org.matrix.android.sdk.api.session.room.model.message.MessageStickerContent import org.matrix.android.sdk.api.session.room.model.message.MessageVideoContent import org.matrix.android.sdk.api.session.room.model.message.MessageWithAttachmentContent import org.matrix.android.sdk.api.session.room.model.message.getFileUrl import org.matrix.android.sdk.api.session.room.model.message.getThumbnailUrl import org.matrix.android.sdk.api.session.room.timeline.TimelineEvent import org.matrix.android.sdk.api.util.MimeTypes -import org.matrix.android.sdk.internal.crypto.attachments.toElementToDecrypt import java.io.File class RoomEventsAttachmentProvider( @@ -52,7 +53,10 @@ class RoomEventsAttachmentProvider( override fun getAttachmentInfoAt(position: Int): AttachmentInfo { return getItem(position).let { - val content = it.root.getClearContent().toModel() as? MessageWithAttachmentContent + val clearContent = it.root.getClearContent() + val content = clearContent.toModel() + ?: clearContent.toModel() + as? MessageWithAttachmentContent if (content is MessageImageContent) { val data = ImageContentRenderer.Data( eventId = it.eventId, @@ -66,6 +70,33 @@ class RoomEventsAttachmentProvider( height = null, allowNonMxcUrls = it.root.sendState.isSending() + ) + if (content.mimeType == MimeTypes.Gif) { + AttachmentInfo.AnimatedImage( + uid = it.eventId, + url = content.url ?: "", + data = data + ) + } else { + AttachmentInfo.Image( + uid = it.eventId, + url = content.url ?: "", + data = data + ) + } + } else if (content is MessageStickerContent) { + val data = ImageContentRenderer.Data( + eventId = it.eventId, + filename = content.body, + mimeType = content.mimeType, + url = content.getFileUrl(), + elementToDecrypt = content.encryptedFileInfo?.toElementToDecrypt(), + maxHeight = -1, + maxWidth = -1, + width = null, + height = null, + allowNonMxcUrls = false + ) if (content.mimeType == MimeTypes.Gif) { AttachmentInfo.AnimatedImage( diff --git a/vector/src/main/java/im/vector/app/features/media/VectorAttachmentViewerActivity.kt b/vector/src/main/java/im/vector/app/features/media/VectorAttachmentViewerActivity.kt index d8c2b83f9b..a9755138de 100644 --- a/vector/src/main/java/im/vector/app/features/media/VectorAttachmentViewerActivity.kt +++ b/vector/src/main/java/im/vector/app/features/media/VectorAttachmentViewerActivity.kt @@ -53,6 +53,7 @@ import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.launch import kotlinx.coroutines.withContext import kotlinx.parcelize.Parcelize +import org.matrix.android.sdk.api.session.getRoom import timber.log.Timber import javax.inject.Inject diff --git a/vector/src/main/java/im/vector/app/features/media/VideoContentRenderer.kt b/vector/src/main/java/im/vector/app/features/media/VideoContentRenderer.kt index 635de2ba16..4e6d19686b 100644 --- a/vector/src/main/java/im/vector/app/features/media/VideoContentRenderer.kt +++ b/vector/src/main/java/im/vector/app/features/media/VideoContentRenderer.kt @@ -31,7 +31,7 @@ import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.launch import kotlinx.coroutines.withContext import kotlinx.parcelize.Parcelize -import org.matrix.android.sdk.internal.crypto.attachments.ElementToDecrypt +import org.matrix.android.sdk.api.session.crypto.attachments.ElementToDecrypt import timber.log.Timber import java.net.URLEncoder import javax.inject.Inject diff --git a/vector/src/main/java/im/vector/app/features/navigation/DefaultNavigator.kt b/vector/src/main/java/im/vector/app/features/navigation/DefaultNavigator.kt index cc02687d93..076b7be3ad 100644 --- a/vector/src/main/java/im/vector/app/features/navigation/DefaultNavigator.kt +++ b/vector/src/main/java/im/vector/app/features/navigation/DefaultNavigator.kt @@ -75,9 +75,9 @@ import im.vector.app.features.onboarding.OnboardingActivity import im.vector.app.features.pin.PinActivity import im.vector.app.features.pin.PinArgs import im.vector.app.features.pin.PinMode +import im.vector.app.features.poll.PollMode import im.vector.app.features.poll.create.CreatePollActivity import im.vector.app.features.poll.create.CreatePollArgs -import im.vector.app.features.poll.create.PollMode import im.vector.app.features.roomdirectory.RoomDirectoryActivity import im.vector.app.features.roomdirectory.RoomDirectoryData import im.vector.app.features.roomdirectory.createroom.CreateRoomActivity @@ -101,6 +101,8 @@ import im.vector.app.features.widgets.WidgetActivity import im.vector.app.features.widgets.WidgetArgsBuilder import im.vector.app.space import org.matrix.android.sdk.api.session.crypto.verification.IncomingSasVerificationTransaction +import org.matrix.android.sdk.api.session.getRoom +import org.matrix.android.sdk.api.session.getRoomSummary import org.matrix.android.sdk.api.session.permalinks.PermalinkData import org.matrix.android.sdk.api.session.room.model.roomdirectory.PublicRoom import org.matrix.android.sdk.api.session.terms.TermsService @@ -176,6 +178,9 @@ class DefaultNavigator @Inject constructor( Navigator.PostSwitchSpaceAction.OpenAddExistingRooms -> { startActivity(context, SpaceManageActivity.newIntent(context, spaceId, ManageType.AddRooms), false) } + Navigator.PostSwitchSpaceAction.OpenRoomList -> { + startActivity(context, SpaceExploreActivity.newIntent(context, spaceId), buildTask = false) + } is Navigator.PostSwitchSpaceAction.OpenDefaultRoom -> { val args = TimelineArgs( postSwitchSpaceAction.roomId, @@ -320,6 +325,7 @@ class DefaultNavigator @Inject constructor( } } } + null -> Unit } } @@ -376,6 +382,7 @@ class DefaultNavigator @Inject constructor( context.startActivity(intent) } } + null -> Unit } } @@ -595,4 +602,9 @@ class DefaultNavigator @Inject constructor( roomEncryptionTrustLevel = threadTimelineArgs.roomEncryptionTrustLevel ))) } + + override fun openScreenSharingPermissionDialog(screenCaptureIntent: Intent, + activityResultLauncher: ActivityResultLauncher) { + activityResultLauncher.launch(screenCaptureIntent) + } } diff --git a/vector/src/main/java/im/vector/app/features/navigation/Navigator.kt b/vector/src/main/java/im/vector/app/features/navigation/Navigator.kt index a31dc8fb89..41b5374168 100644 --- a/vector/src/main/java/im/vector/app/features/navigation/Navigator.kt +++ b/vector/src/main/java/im/vector/app/features/navigation/Navigator.kt @@ -31,7 +31,7 @@ import im.vector.app.features.location.LocationSharingMode import im.vector.app.features.login.LoginConfig import im.vector.app.features.media.AttachmentData import im.vector.app.features.pin.PinMode -import im.vector.app.features.poll.create.PollMode +import im.vector.app.features.poll.PollMode import im.vector.app.features.roomdirectory.RoomDirectoryData import im.vector.app.features.roomdirectory.roompreview.RoomPreviewData import im.vector.app.features.settings.VectorSettingsActivity @@ -54,8 +54,9 @@ interface Navigator { sealed class PostSwitchSpaceAction { object None : PostSwitchSpaceAction() - data class OpenDefaultRoom(val roomId: String, val showShareSheet: Boolean) : PostSwitchSpaceAction() object OpenAddExistingRooms : PostSwitchSpaceAction() + object OpenRoomList : PostSwitchSpaceAction() + data class OpenDefaultRoom(val roomId: String, val showShareSheet: Boolean) : PostSwitchSpaceAction() } fun switchToSpace(context: Context, spaceId: String, postSwitchSpaceAction: PostSwitchSpaceAction) @@ -167,4 +168,9 @@ interface Navigator { fun openThread(context: Context, threadTimelineArgs: ThreadTimelineArgs, eventIdToNavigate: String? = null) fun openThreadList(context: Context, threadTimelineArgs: ThreadTimelineArgs) + + fun openScreenSharingPermissionDialog( + screenCaptureIntent: Intent, + activityResultLauncher: ActivityResultLauncher + ) } diff --git a/vector/src/main/java/im/vector/app/features/notifications/NotifiableEventResolver.kt b/vector/src/main/java/im/vector/app/features/notifications/NotifiableEventResolver.kt index 3c9b985df5..43eab0c1f2 100644 --- a/vector/src/main/java/im/vector/app/features/notifications/NotifiableEventResolver.kt +++ b/vector/src/main/java/im/vector/app/features/notifications/NotifiableEventResolver.kt @@ -27,12 +27,16 @@ import org.matrix.android.sdk.api.extensions.orFalse import org.matrix.android.sdk.api.session.Session import org.matrix.android.sdk.api.session.content.ContentUrlResolver import org.matrix.android.sdk.api.session.crypto.MXCryptoError +import org.matrix.android.sdk.api.session.crypto.model.OlmDecryptionResult import org.matrix.android.sdk.api.session.events.model.Event import org.matrix.android.sdk.api.session.events.model.EventType import org.matrix.android.sdk.api.session.events.model.isEdition import org.matrix.android.sdk.api.session.events.model.isImageMessage import org.matrix.android.sdk.api.session.events.model.supportsNotification import org.matrix.android.sdk.api.session.events.model.toModel +import org.matrix.android.sdk.api.session.getRoom +import org.matrix.android.sdk.api.session.getRoomSummary +import org.matrix.android.sdk.api.session.getUser import org.matrix.android.sdk.api.session.room.model.Membership import org.matrix.android.sdk.api.session.room.model.RoomMemberContent import org.matrix.android.sdk.api.session.room.model.message.MessageWithAttachmentContent @@ -41,7 +45,6 @@ import org.matrix.android.sdk.api.session.room.timeline.TimelineEvent import org.matrix.android.sdk.api.session.room.timeline.getEditedEventId import org.matrix.android.sdk.api.session.room.timeline.getLastMessageContent import org.matrix.android.sdk.api.util.toMatrixItem -import org.matrix.android.sdk.internal.crypto.algorithms.olm.OlmDecryptionResult import timber.log.Timber import java.util.UUID import javax.inject.Inject @@ -100,7 +103,7 @@ class NotifiableEventResolver @Inject constructor( // Ignore message edition if (event.isEdition()) return null - val actions = session.getActions(event) + val actions = session.pushRuleService().getActions(event) val notificationAction = actions.toNotificationAction() return if (notificationAction.shouldNotify) { @@ -155,7 +158,8 @@ class NotifiableEventResolver @Inject constructor( // only convert encrypted messages to NotifiableMessageEvents when (event.root.getClearType()) { EventType.MESSAGE, - in EventType.POLL_START -> { + in EventType.POLL_START, + in EventType.STATE_ROOM_BEACON_INFO -> { val body = displayableEventFormatter.format(event, isDm = room.roomSummary()?.isDirect.orFalse(), appendAuthor = false).toString() val roomName = room.roomSummary()?.displayName ?: "" val senderDisplayName = event.senderInfo.disambiguatedDisplayName @@ -187,7 +191,7 @@ class NotifiableEventResolver @Inject constructor( soundName = null ) } - else -> null + else -> null } } } @@ -232,7 +236,7 @@ class NotifiableEventResolver @Inject constructor( private fun resolveStateRoomEvent(event: Event, session: Session, canBeReplaced: Boolean, isNoisy: Boolean): NotifiableEvent? { val content = event.content?.toModel() ?: return null val roomId = event.roomId ?: return null - val dName = event.senderId?.let { session.getRoomMember(it, roomId)?.displayName } + val dName = event.senderId?.let { session.roomService().getRoomMember(it, roomId)?.displayName } if (Membership.INVITE == content.membership) { val roomSummary = session.getRoomSummary(roomId) val body = noticeEventFormatter.format(event, dName, isDm = roomSummary?.isDirect.orFalse()) diff --git a/vector/src/main/java/im/vector/app/features/notifications/NotificationAction.kt b/vector/src/main/java/im/vector/app/features/notifications/NotificationAction.kt index c11ce6794f..86a9e917e5 100644 --- a/vector/src/main/java/im/vector/app/features/notifications/NotificationAction.kt +++ b/vector/src/main/java/im/vector/app/features/notifications/NotificationAction.kt @@ -15,7 +15,7 @@ */ package im.vector.app.features.notifications -import org.matrix.android.sdk.api.pushrules.Action +import org.matrix.android.sdk.api.session.pushrules.Action data class NotificationAction( val shouldNotify: Boolean, diff --git a/vector/src/main/java/im/vector/app/features/notifications/NotificationBroadcastReceiver.kt b/vector/src/main/java/im/vector/app/features/notifications/NotificationBroadcastReceiver.kt index 01c1117ab2..3d5bd7930c 100644 --- a/vector/src/main/java/im/vector/app/features/notifications/NotificationBroadcastReceiver.kt +++ b/vector/src/main/java/im/vector/app/features/notifications/NotificationBroadcastReceiver.kt @@ -29,6 +29,7 @@ import im.vector.app.features.session.coroutineScope import kotlinx.coroutines.launch import org.matrix.android.sdk.api.extensions.tryOrNull import org.matrix.android.sdk.api.session.Session +import org.matrix.android.sdk.api.session.getRoom import org.matrix.android.sdk.api.session.room.Room import org.matrix.android.sdk.api.session.room.read.ReadService import timber.log.Timber @@ -83,7 +84,7 @@ class NotificationBroadcastReceiver : BroadcastReceiver() { if (room != null) { session.coroutineScope.launch { tryOrNull { - session.joinRoom(room.roomId) + session.roomService().joinRoom(room.roomId) analyticsTracker.capture(room.roomSummary().toAnalyticsJoinedRoom()) } } @@ -94,7 +95,7 @@ class NotificationBroadcastReceiver : BroadcastReceiver() { private fun handleRejectRoom(roomId: String) { activeSessionHolder.getSafeActiveSession()?.let { session -> session.coroutineScope.launch { - tryOrNull { session.leaveRoom(roomId) } + tryOrNull { session.roomService().leaveRoom(roomId) } } } } @@ -137,7 +138,7 @@ class NotificationBroadcastReceiver : BroadcastReceiver() { editedEventId = null, noisy = false, timestamp = System.currentTimeMillis(), - senderName = session.getRoomMember(session.myUserId, room.roomId)?.displayName + senderName = session.roomService().getRoomMember(session.myUserId, room.roomId)?.displayName ?: context?.getString(R.string.notification_sender_me), senderId = session.myUserId, body = message, @@ -202,7 +203,7 @@ class NotificationBroadcastReceiver : BroadcastReceiver() { VectorApp.getInstance().notificationDrawerManager.refreshNotificationDrawer(null) } }) - */ + */ } private fun getReplyMessage(intent: Intent?): String? { diff --git a/vector/src/main/java/im/vector/app/features/notifications/NotificationDrawerManager.kt b/vector/src/main/java/im/vector/app/features/notifications/NotificationDrawerManager.kt index fa56e3b8ed..62cac7507f 100644 --- a/vector/src/main/java/im/vector/app/features/notifications/NotificationDrawerManager.kt +++ b/vector/src/main/java/im/vector/app/features/notifications/NotificationDrawerManager.kt @@ -27,6 +27,7 @@ import im.vector.app.features.displayname.getBestName import im.vector.app.features.settings.VectorPreferences import org.matrix.android.sdk.api.session.Session import org.matrix.android.sdk.api.session.content.ContentUrlResolver +import org.matrix.android.sdk.api.session.getUser import org.matrix.android.sdk.api.util.toMatrixItem import timber.log.Timber import javax.inject.Inject diff --git a/vector/src/main/java/im/vector/app/features/notifications/NotificationEventPersistence.kt b/vector/src/main/java/im/vector/app/features/notifications/NotificationEventPersistence.kt index a4a7570fec..5a716ff173 100644 --- a/vector/src/main/java/im/vector/app/features/notifications/NotificationEventPersistence.kt +++ b/vector/src/main/java/im/vector/app/features/notifications/NotificationEventPersistence.kt @@ -34,7 +34,7 @@ class NotificationEventPersistence @Inject constructor(private val context: Cont val file = File(context.applicationContext.cacheDir, ROOMS_NOTIFICATIONS_FILE_NAME) if (file.exists()) { file.inputStream().use { - val events: ArrayList? = currentSession?.loadSecureSecret(it, KEY_ALIAS_SECRET_STORAGE) + val events: ArrayList? = currentSession?.secureStorageService()?.loadSecureSecret(it, KEY_ALIAS_SECRET_STORAGE) if (events != null) { return factory(events) } @@ -55,7 +55,7 @@ class NotificationEventPersistence @Inject constructor(private val context: Cont val file = File(context.applicationContext.cacheDir, ROOMS_NOTIFICATIONS_FILE_NAME) if (!file.exists()) file.createNewFile() FileOutputStream(file).use { - currentSession.securelyStoreObject(queuedEvents.rawEvents(), KEY_ALIAS_SECRET_STORAGE, it) + currentSession.secureStorageService().securelyStoreObject(queuedEvents.rawEvents(), KEY_ALIAS_SECRET_STORAGE, it) } } catch (e: Throwable) { Timber.e(e, "## Failed to save cached notification info") diff --git a/vector/src/main/java/im/vector/app/features/notifications/NotificationRenderer.kt b/vector/src/main/java/im/vector/app/features/notifications/NotificationRenderer.kt index 4078bb0b5c..e0e21a39a7 100644 --- a/vector/src/main/java/im/vector/app/features/notifications/NotificationRenderer.kt +++ b/vector/src/main/java/im/vector/app/features/notifications/NotificationRenderer.kt @@ -47,11 +47,9 @@ class NotificationRenderer @Inject constructor(private val notificationDisplayer ) // Remove summary first to avoid briefly displaying it after dismissing the last notification - when (summaryNotification) { - SummaryNotification.Removed -> { - Timber.d("Removing summary notification") - notificationDisplayer.cancelNotificationMessage(null, SUMMARY_NOTIFICATION_ID) - } + if (summaryNotification == SummaryNotification.Removed) { + Timber.d("Removing summary notification") + notificationDisplayer.cancelNotificationMessage(null, SUMMARY_NOTIFICATION_ID) } roomNotifications.forEach { wrapper -> @@ -94,11 +92,9 @@ class NotificationRenderer @Inject constructor(private val notificationDisplayer } // Update summary last to avoid briefly displaying it before other notifications - when (summaryNotification) { - is SummaryNotification.Update -> { - Timber.d("Updating summary notification") - notificationDisplayer.showNotificationMessage(null, SUMMARY_NOTIFICATION_ID, summaryNotification.notification) - } + if (summaryNotification is SummaryNotification.Update) { + Timber.d("Updating summary notification") + notificationDisplayer.showNotificationMessage(null, SUMMARY_NOTIFICATION_ID, summaryNotification.notification) } } } diff --git a/vector/src/main/java/im/vector/app/features/notifications/NotificationUtils.kt b/vector/src/main/java/im/vector/app/features/notifications/NotificationUtils.kt index d39926f620..e44f42d5cd 100755 --- a/vector/src/main/java/im/vector/app/features/notifications/NotificationUtils.kt +++ b/vector/src/main/java/im/vector/app/features/notifications/NotificationUtils.kt @@ -521,6 +521,34 @@ class NotificationUtils @Inject constructor(private val context: Context, return builder.build() } + /** + * Creates a notification that indicates the application is retrieving location even if it is in background or killed. + */ + fun buildLiveLocationSharingNotification(): Notification { + return NotificationCompat.Builder(context, SILENT_NOTIFICATION_CHANNEL_ID) + .setContentTitle(stringProvider.getString(R.string.live_location_sharing_notification_title)) + .setContentText(stringProvider.getString(R.string.live_location_sharing_notification_description)) + .setSmallIcon(R.drawable.ic_attachment_location_live_white) + .setColor(ThemeUtils.getColor(context, android.R.attr.colorPrimary)) + .setCategory(NotificationCompat.CATEGORY_LOCATION_SHARING) + .setContentIntent(buildOpenHomePendingIntentForSummary()) + .build() + } + + /** + * Creates a notification that indicates the application is capturing the screen. + */ + fun buildScreenSharingNotification(): Notification { + return NotificationCompat.Builder(context, SILENT_NOTIFICATION_CHANNEL_ID) + .setContentTitle(stringProvider.getString(R.string.screen_sharing_notification_title)) + .setContentText(stringProvider.getString(R.string.screen_sharing_notification_description)) + .setSmallIcon(R.drawable.ic_share_screen) + .setColor(ThemeUtils.getColor(context, android.R.attr.colorPrimary)) + .setCategory(NotificationCompat.CATEGORY_SERVICE) + .setContentIntent(buildOpenHomePendingIntentForSummary()) + .build() + } + fun buildDownloadFileNotification(uri: Uri, fileName: String, mimeType: String): Notification { return NotificationCompat.Builder(context, SILENT_NOTIFICATION_CHANNEL_ID) .setGroup(stringProvider.getString(R.string.app_name)) diff --git a/vector/src/main/java/im/vector/app/features/notifications/OutdatedEventDetector.kt b/vector/src/main/java/im/vector/app/features/notifications/OutdatedEventDetector.kt index c6ef616afe..5a9a950a02 100644 --- a/vector/src/main/java/im/vector/app/features/notifications/OutdatedEventDetector.kt +++ b/vector/src/main/java/im/vector/app/features/notifications/OutdatedEventDetector.kt @@ -16,6 +16,7 @@ package im.vector.app.features.notifications import im.vector.app.ActiveSessionDataSource +import org.matrix.android.sdk.api.session.getRoom import javax.inject.Inject class OutdatedEventDetector @Inject constructor( diff --git a/vector/src/main/java/im/vector/app/features/notifications/PushRuleTriggerListener.kt b/vector/src/main/java/im/vector/app/features/notifications/PushRuleTriggerListener.kt index cd08820fc1..58f895bf0c 100644 --- a/vector/src/main/java/im/vector/app/features/notifications/PushRuleTriggerListener.kt +++ b/vector/src/main/java/im/vector/app/features/notifications/PushRuleTriggerListener.kt @@ -21,10 +21,10 @@ import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.SupervisorJob import kotlinx.coroutines.cancelChildren import kotlinx.coroutines.launch -import org.matrix.android.sdk.api.pushrules.PushEvents -import org.matrix.android.sdk.api.pushrules.PushRuleService -import org.matrix.android.sdk.api.pushrules.getActions import org.matrix.android.sdk.api.session.Session +import org.matrix.android.sdk.api.session.pushrules.PushEvents +import org.matrix.android.sdk.api.session.pushrules.PushRuleService +import org.matrix.android.sdk.api.session.pushrules.getActions import timber.log.Timber import javax.inject.Inject import javax.inject.Singleton @@ -71,12 +71,12 @@ class PushRuleTriggerListener @Inject constructor( stop() } this.session = session - session.addPushRuleListener(this) + session.pushRuleService().addPushRuleListener(this) } fun stop() { scope.coroutineContext.cancelChildren(CancellationException("PushRuleTriggerListener stopping")) - session?.removePushRuleListener(this) + session?.pushRuleService()?.removePushRuleListener(this) session = null notificationDrawerManager.clearAllEvents() } diff --git a/vector/src/main/java/im/vector/app/features/onboarding/DirectLoginUseCase.kt b/vector/src/main/java/im/vector/app/features/onboarding/DirectLoginUseCase.kt new file mode 100644 index 0000000000..171d8f7bb5 --- /dev/null +++ b/vector/src/main/java/im/vector/app/features/onboarding/DirectLoginUseCase.kt @@ -0,0 +1,84 @@ +/* + * Copyright (c) 2022 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package im.vector.app.features.onboarding + +import im.vector.app.R +import im.vector.app.core.extensions.andThen +import im.vector.app.core.resources.StringProvider +import im.vector.app.features.onboarding.OnboardingAction.LoginOrRegister +import org.matrix.android.sdk.api.MatrixPatterns.getDomain +import org.matrix.android.sdk.api.auth.AuthenticationService +import org.matrix.android.sdk.api.auth.data.HomeServerConnectionConfig +import org.matrix.android.sdk.api.auth.wellknown.WellknownResult +import org.matrix.android.sdk.api.session.Session +import javax.inject.Inject + +class DirectLoginUseCase @Inject constructor( + private val authenticationService: AuthenticationService, + private val stringProvider: StringProvider, + private val uriFactory: UriFactory +) { + + suspend fun execute(action: LoginOrRegister, homeServerConnectionConfig: HomeServerConnectionConfig?): Result { + return fetchWellKnown(action.username, homeServerConnectionConfig) + .andThen { wellKnown -> createSessionFor(wellKnown, action, homeServerConnectionConfig) } + } + + private suspend fun fetchWellKnown(matrixId: String, config: HomeServerConnectionConfig?) = runCatching { + authenticationService.getWellKnownData(matrixId, config) + } + + private suspend fun createSessionFor(data: WellknownResult, action: LoginOrRegister, config: HomeServerConnectionConfig?) = when (data) { + is WellknownResult.Prompt -> loginDirect(action, data, config) + is WellknownResult.FailPrompt -> handleFailPrompt(data, action, config) + else -> onWellKnownError() + } + + private suspend fun handleFailPrompt(data: WellknownResult.FailPrompt, action: LoginOrRegister, config: HomeServerConnectionConfig?): Result { + // Relax on IS discovery if homeserver is valid + val isMissingInformationToLogin = data.homeServerUrl == null || data.wellKnown == null + return when { + isMissingInformationToLogin -> onWellKnownError() + else -> loginDirect(action, WellknownResult.Prompt(data.homeServerUrl!!, null, data.wellKnown!!), config) + } + } + + private suspend fun loginDirect(action: LoginOrRegister, wellKnownPrompt: WellknownResult.Prompt, config: HomeServerConnectionConfig?): Result { + val alteredHomeServerConnectionConfig = config?.updateWith(wellKnownPrompt) ?: fallbackConfig(action, wellKnownPrompt) + return runCatching { + authenticationService.directAuthentication( + alteredHomeServerConnectionConfig, + action.username, + action.password, + action.initialDeviceName + ) + } + } + + private fun HomeServerConnectionConfig.updateWith(wellKnownPrompt: WellknownResult.Prompt) = copy( + homeServerUriBase = uriFactory.parse(wellKnownPrompt.homeServerUrl), + identityServerUri = wellKnownPrompt.identityServerUrl?.let { uriFactory.parse(it) } + ) + + private fun fallbackConfig(action: LoginOrRegister, wellKnownPrompt: WellknownResult.Prompt) = HomeServerConnectionConfig( + homeServerUri = uriFactory.parse("https://${action.username.getDomain()}"), + homeServerUriBase = uriFactory.parse(wellKnownPrompt.homeServerUrl), + identityServerUri = wellKnownPrompt.identityServerUrl?.let { uriFactory.parse(it) } + ) + + private fun onWellKnownError() = Result.failure(Exception(stringProvider.getString(R.string.autodiscover_well_known_error))) +} diff --git a/vector/src/main/java/im/vector/app/features/onboarding/Login2Variant.kt b/vector/src/main/java/im/vector/app/features/onboarding/Login2Variant.kt index 107c08da5a..15d07a0197 100644 --- a/vector/src/main/java/im/vector/app/features/onboarding/Login2Variant.kt +++ b/vector/src/main/java/im/vector/app/features/onboarding/Login2Variant.kt @@ -30,7 +30,6 @@ import im.vector.app.R import im.vector.app.core.extensions.POP_BACK_STACK_EXCLUSIVE import im.vector.app.core.extensions.addFragment import im.vector.app.core.extensions.addFragmentToBackstack -import im.vector.app.core.extensions.exhaustive import im.vector.app.core.extensions.resetBackstack import im.vector.app.core.platform.VectorBaseActivity import im.vector.app.databinding.ActivityLoginBinding @@ -42,7 +41,6 @@ import im.vector.app.features.login.LoginWaitForEmailFragmentArgument import im.vector.app.features.login.TextInputFormFragmentMode import im.vector.app.features.login.isSupported import im.vector.app.features.login.terms.LoginTermsFragmentArgument -import im.vector.app.features.login.terms.toLocalizedLoginTerms import im.vector.app.features.login2.LoginAction2 import im.vector.app.features.login2.LoginCaptchaFragment2 import im.vector.app.features.login2.LoginFragmentSigninPassword2 @@ -67,6 +65,7 @@ import im.vector.app.features.login2.created.AccountCreatedFragment import im.vector.app.features.login2.terms.LoginTermsFragment2 import org.matrix.android.sdk.api.auth.registration.FlowResult import org.matrix.android.sdk.api.auth.registration.Stage +import org.matrix.android.sdk.api.auth.toLocalizedLoginTerms import org.matrix.android.sdk.api.extensions.tryOrNull private const val FRAGMENT_REGISTRATION_STAGE_TAG = "FRAGMENT_REGISTRATION_STAGE_TAG" @@ -257,7 +256,7 @@ class Login2Variant( is LoginViewEvents2.OnSessionCreated -> handleOnSessionCreated(event) is LoginViewEvents2.Finish -> terminate(true) is LoginViewEvents2.CancelRegistration -> handleCancelRegistration() - }.exhaustive + } } private fun handleCancelRegistration() { diff --git a/vector/src/main/java/im/vector/app/features/onboarding/OnboardingAction.kt b/vector/src/main/java/im/vector/app/features/onboarding/OnboardingAction.kt index 7fa75d1544..9f7dce56ea 100644 --- a/vector/src/main/java/im/vector/app/features/onboarding/OnboardingAction.kt +++ b/vector/src/main/java/im/vector/app/features/onboarding/OnboardingAction.kt @@ -22,14 +22,21 @@ import im.vector.app.features.login.LoginConfig import im.vector.app.features.login.ServerType import im.vector.app.features.login.SignMode import org.matrix.android.sdk.api.auth.data.Credentials -import org.matrix.android.sdk.internal.network.ssl.Fingerprint +import org.matrix.android.sdk.api.network.ssl.Fingerprint sealed interface OnboardingAction : VectorViewModelAction { data class OnGetStarted(val resetLoginConfig: Boolean, val onboardingFlow: OnboardingFlow) : OnboardingAction data class OnIAlreadyHaveAnAccount(val resetLoginConfig: Boolean, val onboardingFlow: OnboardingFlow) : OnboardingAction data class UpdateServerType(val serverType: ServerType) : OnboardingAction - data class UpdateHomeServer(val homeServerUrl: String) : OnboardingAction + + sealed interface HomeServerChange : OnboardingAction { + val homeServerUrl: String + + data class SelectHomeServer(override val homeServerUrl: String) : HomeServerChange + data class EditHomeServer(override val homeServerUrl: String) : HomeServerChange + } + data class UpdateUseCase(val useCase: FtueUseCase) : OnboardingAction object ResetUseCase : OnboardingAction data class UpdateSignMode(val signMode: SignMode) : OnboardingAction @@ -41,6 +48,7 @@ sealed interface OnboardingAction : VectorViewModelAction { // Login or Register, depending on the signMode data class LoginOrRegister(val username: String, val password: String, val initialDeviceName: String) : OnboardingAction + data class Register(val username: String, val password: String, val initialDeviceName: String) : OnboardingAction object StopEmailValidationCheck : OnboardingAction data class PostRegisterAction(val registerAction: RegisterAction) : OnboardingAction @@ -51,7 +59,7 @@ sealed interface OnboardingAction : VectorViewModelAction { object ResetHomeServerType : ResetAction object ResetHomeServerUrl : ResetAction object ResetSignMode : ResetAction - object ResetLogin : ResetAction + object ResetAuthenticationAttempt : ResetAction object ResetResetPassword : ResetAction // Homeserver history diff --git a/vector/src/main/java/im/vector/app/features/onboarding/OnboardingViewEvents.kt b/vector/src/main/java/im/vector/app/features/onboarding/OnboardingViewEvents.kt index 82ee48411d..ee406aff48 100644 --- a/vector/src/main/java/im/vector/app/features/onboarding/OnboardingViewEvents.kt +++ b/vector/src/main/java/im/vector/app/features/onboarding/OnboardingViewEvents.kt @@ -36,8 +36,11 @@ sealed class OnboardingViewEvents : VectorViewEvents { object OpenUseCaseSelection : OnboardingViewEvents() object OpenServerSelection : OnboardingViewEvents() + object OpenCombinedRegister : OnboardingViewEvents() + object EditServerSelection : OnboardingViewEvents() data class OnServerSelectionDone(val serverType: ServerType) : OnboardingViewEvents() object OnLoginFlowRetrieved : OnboardingViewEvents() + object OnHomeserverEdited : OnboardingViewEvents() data class OnSignModeSelected(val signMode: SignMode) : OnboardingViewEvents() object OnForgetPasswordClicked : OnboardingViewEvents() object OnResetPasswordSendThreePidDone : OnboardingViewEvents() diff --git a/vector/src/main/java/im/vector/app/features/onboarding/OnboardingViewModel.kt b/vector/src/main/java/im/vector/app/features/onboarding/OnboardingViewModel.kt index 6659058b4e..82835849c0 100644 --- a/vector/src/main/java/im/vector/app/features/onboarding/OnboardingViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/onboarding/OnboardingViewModel.kt @@ -17,12 +17,7 @@ package im.vector.app.features.onboarding import android.content.Context -import android.net.Uri -import com.airbnb.mvrx.Fail -import com.airbnb.mvrx.Loading import com.airbnb.mvrx.MavericksViewModelFactory -import com.airbnb.mvrx.Success -import com.airbnb.mvrx.Uninitialized import dagger.assisted.Assisted import dagger.assisted.AssistedFactory import dagger.assisted.AssistedInject @@ -31,7 +26,6 @@ import im.vector.app.core.di.ActiveSessionHolder import im.vector.app.core.di.MavericksAssistedViewModelFactory import im.vector.app.core.di.hiltMavericksViewModelFactory import im.vector.app.core.extensions.configureAndStart -import im.vector.app.core.extensions.exhaustive import im.vector.app.core.extensions.vectorStore import im.vector.app.core.platform.VectorViewModel import im.vector.app.core.resources.StringProvider @@ -47,22 +41,18 @@ import im.vector.app.features.login.LoginMode import im.vector.app.features.login.ReAuthHelper import im.vector.app.features.login.ServerType import im.vector.app.features.login.SignMode +import im.vector.app.features.onboarding.StartAuthenticationFlowUseCase.StartAuthenticationResult import kotlinx.coroutines.Job import kotlinx.coroutines.flow.firstOrNull import kotlinx.coroutines.launch -import org.matrix.android.sdk.api.MatrixPatterns.getDomain import org.matrix.android.sdk.api.auth.AuthenticationService import org.matrix.android.sdk.api.auth.HomeServerHistoryService import org.matrix.android.sdk.api.auth.data.HomeServerConnectionConfig -import org.matrix.android.sdk.api.auth.data.LoginFlowTypes import org.matrix.android.sdk.api.auth.login.LoginWizard import org.matrix.android.sdk.api.auth.registration.FlowResult import org.matrix.android.sdk.api.auth.registration.RegistrationResult import org.matrix.android.sdk.api.auth.registration.RegistrationWizard import org.matrix.android.sdk.api.auth.registration.Stage -import org.matrix.android.sdk.api.auth.wellknown.WellknownResult -import org.matrix.android.sdk.api.failure.Failure -import org.matrix.android.sdk.api.failure.MatrixIdFailure import org.matrix.android.sdk.api.session.Session import timber.log.Timber import java.util.UUID @@ -84,6 +74,8 @@ class OnboardingViewModel @AssistedInject constructor( private val analyticsTracker: AnalyticsTracker, private val uriFilenameResolver: UriFilenameResolver, private val registrationActionHandler: RegistrationActionHandler, + private val directLoginUseCase: DirectLoginUseCase, + private val startAuthenticationFlowUseCase: StartAuthenticationFlowUseCase, private val vectorOverrides: VectorOverrides ) : VectorViewModel(initialState) { @@ -116,6 +108,7 @@ class OnboardingViewModel @AssistedInject constructor( private var currentHomeServerConnectionConfig: HomeServerConnectionConfig? = null private val matrixOrgUrl = stringProvider.getString(R.string.matrix_org_server_url).ensureTrailingSlash() + private val defaultHomeserverUrl = matrixOrgUrl private val registrationWizard: RegistrationWizard get() = authenticationService.getRegistrationWizard() @@ -148,13 +141,14 @@ class OnboardingViewModel @AssistedInject constructor( is OnboardingAction.UpdateServerType -> handleUpdateServerType(action) is OnboardingAction.UpdateSignMode -> handleUpdateSignMode(action) is OnboardingAction.InitWith -> handleInitWith(action) - is OnboardingAction.UpdateHomeServer -> handleUpdateHomeserver(action).also { lastAction = action } + is OnboardingAction.HomeServerChange -> withAction(action) { handleHomeserverChange(action) } is OnboardingAction.LoginOrRegister -> handleLoginOrRegister(action).also { lastAction = action } + is OnboardingAction.Register -> handleRegisterWith(action).also { lastAction = action } is OnboardingAction.LoginWithToken -> handleLoginWithToken(action) is OnboardingAction.WebLoginSuccess -> handleWebLoginSuccess(action) is OnboardingAction.ResetPassword -> handleResetPassword(action) is OnboardingAction.ResetPasswordMailConfirmed -> handleResetPasswordMailConfirmed() - is OnboardingAction.PostRegisterAction -> handleRegisterAction(action.registerAction) + is OnboardingAction.PostRegisterAction -> handleRegisterAction(action.registerAction, ::emitFlowResultViewEvent) is OnboardingAction.ResetAction -> handleResetAction(action) is OnboardingAction.UserAcceptCertificate -> handleUserAcceptCertificate(action) OnboardingAction.ClearHomeServerHistory -> handleClearHomeServerHistory() @@ -166,7 +160,12 @@ class OnboardingViewModel @AssistedInject constructor( OnboardingAction.SaveSelectedProfilePicture -> updateProfilePicture() is OnboardingAction.PostViewEvent -> _viewEvents.post(action.viewEvent) OnboardingAction.StopEmailValidationCheck -> cancelWaitForEmailValidation() - }.exhaustive + } + } + + private fun withAction(action: OnboardingAction, block: (OnboardingAction) -> Unit) { + lastAction = action + block(action) } private fun handleSplashAction(resetConfig: Boolean, onboardingFlow: OnboardingFlow) { @@ -175,19 +174,19 @@ class OnboardingViewModel @AssistedInject constructor( } setState { copy(onboardingFlow = onboardingFlow) } - val configUrl = loginConfig?.homeServerUrl?.takeIf { it.isNotEmpty() } - if (configUrl != null) { - // Use config from uri - val homeServerConnectionConfig = homeServerConnectionConfigFactory.create(configUrl) - if (homeServerConnectionConfig == null) { - // Url is invalid, in this case, just use the regular flow - Timber.w("Url from config url was invalid: $configUrl") - continueToPageAfterSplash(onboardingFlow) - } else { - getLoginFlow(homeServerConnectionConfig, ServerType.Other) + return when (val config = loginConfig.toHomeserverConfig()) { + null -> continueToPageAfterSplash(onboardingFlow) + else -> startAuthenticationFlow(trigger = null, config, ServerType.Other) + } + } + + private fun LoginConfig?.toHomeserverConfig(): HomeServerConnectionConfig? { + return this?.homeServerUrl?.takeIf { it.isNotEmpty() }?.let { url -> + homeServerConnectionConfigFactory.create(url).also { + if (it == null) { + Timber.w("Url from config url was invalid: $url") + } } - } else { - continueToPageAfterSplash(onboardingFlow) } } @@ -208,12 +207,12 @@ class OnboardingViewModel @AssistedInject constructor( // It happens when we get the login flow, or during direct authentication. // So alter the homeserver config and retrieve again the login flow when (val finalLastAction = lastAction) { - is OnboardingAction.UpdateHomeServer -> { + is OnboardingAction.HomeServerChange.SelectHomeServer -> { currentHomeServerConnectionConfig ?.let { it.copy(allowedFingerprints = it.allowedFingerprints + action.fingerprint) } - ?.let { getLoginFlow(it) } + ?.let { startAuthenticationFlow(finalLastAction, it) } } - is OnboardingAction.LoginOrRegister -> + is OnboardingAction.LoginOrRegister -> handleDirectLogin( finalLastAction, HomeServerConnectionConfig.Builder() @@ -222,6 +221,7 @@ class OnboardingViewModel @AssistedInject constructor( .withAllowedFingerPrints(listOf(action.fingerprint)) .build() ) + else -> Unit } } @@ -239,70 +239,69 @@ class OnboardingViewModel @AssistedInject constructor( val safeLoginWizard = loginWizard if (safeLoginWizard == null) { - setState { - copy( - asyncLoginAction = Fail(Throwable("Bad configuration")) - ) - } + setState { copy(isLoading = false) } + _viewEvents.post(OnboardingViewEvents.Failure(Throwable("Bad configuration"))) } else { - setState { - copy( - asyncLoginAction = Loading() - ) - } + setState { copy(isLoading = true) } currentJob = viewModelScope.launch { try { - safeLoginWizard.loginWithToken(action.loginToken) + val result = safeLoginWizard.loginWithToken(action.loginToken) + onSessionCreated(result, isAccountCreated = false) } catch (failure: Throwable) { + setState { copy(isLoading = false) } _viewEvents.post(OnboardingViewEvents.Failure(failure)) - setState { - copy( - asyncLoginAction = Fail(failure) - ) - } - null } - ?.let { onSessionCreated(it, isAccountCreated = false) } } } } - private fun handleRegisterAction(action: RegisterAction) { + private fun handleRegisterAction(action: RegisterAction, onNextRegistrationStepAction: (FlowResult) -> Unit) { currentJob = viewModelScope.launch { if (action.hasLoadingState()) { - setState { copy(asyncRegistration = Loading()) } + setState { copy(isLoading = true) } } - runCatching { registrationActionHandler.handleRegisterAction(registrationWizard, action) } - .fold( - onSuccess = { - when { - action.ignoresResult() -> { - // do nothing - } - else -> when (it) { - is RegistrationResult.Success -> onSessionCreated(it.session, isAccountCreated = true) - is RegistrationResult.FlowResponse -> onFlowResponse(it.flowResult) - } - } - }, - onFailure = { - if (it !is CancellationException) { - _viewEvents.post(OnboardingViewEvents.Failure(it)) - } - } - ) - setState { copy(asyncRegistration = Uninitialized) } + internalRegisterAction(action, onNextRegistrationStepAction) + setState { copy(isLoading = false) } } } - private fun handleRegisterWith(action: OnboardingAction.LoginOrRegister) { + private suspend fun internalRegisterAction(action: RegisterAction, onNextRegistrationStepAction: (FlowResult) -> Unit) { + runCatching { registrationActionHandler.handleRegisterAction(registrationWizard, action) } + .fold( + onSuccess = { + when { + action.ignoresResult() -> { + // do nothing + } + else -> when (it) { + is RegistrationResult.Success -> onSessionCreated(it.session, isAccountCreated = true) + is RegistrationResult.FlowResponse -> onFlowResponse(it.flowResult, onNextRegistrationStepAction) + } + } + }, + onFailure = { + if (it !is CancellationException) { + _viewEvents.post(OnboardingViewEvents.Failure(it)) + } + } + ) + } + + private fun emitFlowResultViewEvent(flowResult: FlowResult) { + _viewEvents.post(OnboardingViewEvents.RegistrationFlowResult(flowResult, isRegistrationStarted)) + } + + private fun handleRegisterWith(action: OnboardingAction.Register) { reAuthHelper.data = action.password - handleRegisterAction(RegisterAction.CreateAccount( - action.username, - action.password, - action.initialDeviceName - )) + handleRegisterAction( + RegisterAction.CreateAccount( + action.username, + action.password, + action.initialDeviceName + ), + ::emitFlowResultViewEvent + ) } private fun handleResetAction(action: OnboardingAction.ResetAction) { @@ -310,54 +309,38 @@ class OnboardingViewModel @AssistedInject constructor( currentJob = null when (action) { - OnboardingAction.ResetHomeServerType -> { - setState { - copy( - serverType = ServerType.Unknown - ) - } + OnboardingAction.ResetHomeServerType -> { + setState { copy(serverType = ServerType.Unknown) } } - OnboardingAction.ResetHomeServerUrl -> { + OnboardingAction.ResetHomeServerUrl -> { viewModelScope.launch { authenticationService.reset() setState { copy( - asyncHomeServerLoginFlowRequest = Uninitialized, - homeServerUrlFromUser = null, - homeServerUrl = null, - loginMode = LoginMode.Unknown, - serverType = ServerType.Unknown, - loginModeSupportedTypes = emptyList() + isLoading = false, + selectedHomeserver = SelectedHomeserverState(), ) } } } - OnboardingAction.ResetSignMode -> { + OnboardingAction.ResetSignMode -> { setState { copy( - asyncHomeServerLoginFlowRequest = Uninitialized, + isLoading = false, signMode = SignMode.Unknown, - loginMode = LoginMode.Unknown, - loginModeSupportedTypes = emptyList() ) } } - OnboardingAction.ResetLogin -> { + OnboardingAction.ResetAuthenticationAttempt -> { viewModelScope.launch { authenticationService.cancelPendingLoginOrRegistration() - setState { - copy( - asyncLoginAction = Uninitialized, - asyncRegistration = Uninitialized - ) - } + setState { copy(isLoading = false) } } } - OnboardingAction.ResetResetPassword -> { + OnboardingAction.ResetResetPassword -> { setState { copy( - asyncResetPassword = Uninitialized, - asyncResetMailConfirmed = Uninitialized, + isLoading = false, resetPasswordEmail = null ) } @@ -366,23 +349,25 @@ class OnboardingViewModel @AssistedInject constructor( } private fun handleUpdateSignMode(action: OnboardingAction.UpdateSignMode) { - setState { - copy( - signMode = action.signMode - ) - } - + updateSignMode(action.signMode) when (action.signMode) { - SignMode.SignUp -> handleRegisterAction(RegisterAction.StartRegistration) + SignMode.SignUp -> handleRegisterAction(RegisterAction.StartRegistration, ::emitFlowResultViewEvent) SignMode.SignIn -> startAuthenticationFlow() SignMode.SignInWithMatrixId -> _viewEvents.post(OnboardingViewEvents.OnSignModeSelected(SignMode.SignInWithMatrixId)) SignMode.Unknown -> Unit } } + private fun updateSignMode(signMode: SignMode) { + setState { copy(signMode = signMode) } + } + private fun handleUpdateUseCase(action: OnboardingAction.UpdateUseCase) { setState { copy(useCase = action.useCase) } - _viewEvents.post(OnboardingViewEvents.OpenServerSelection) + when (vectorFeatures.isOnboardingCombinedRegisterEnabled()) { + true -> handle(OnboardingAction.HomeServerChange.SelectHomeServer(defaultHomeserverUrl)) + false -> _viewEvents.post(OnboardingViewEvents.OpenServerSelection) + } } private fun resetUseCase() { @@ -400,10 +385,10 @@ class OnboardingViewModel @AssistedInject constructor( ServerType.Unknown -> Unit /* Should not happen */ ServerType.MatrixOrg -> // Request login flow here - handle(OnboardingAction.UpdateHomeServer(matrixOrgUrl)) + handle(OnboardingAction.HomeServerChange.SelectHomeServer(matrixOrgUrl)) ServerType.EMS, ServerType.Other -> _viewEvents.post(OnboardingViewEvents.OnServerSelectionDone(action.serverType)) - }.exhaustive + } } private fun handleInitWith(action: OnboardingAction.InitWith) { @@ -426,35 +411,23 @@ class OnboardingViewModel @AssistedInject constructor( val safeLoginWizard = loginWizard if (safeLoginWizard == null) { - setState { - copy( - asyncResetPassword = Fail(Throwable("Bad configuration")), - asyncResetMailConfirmed = Uninitialized - ) - } + setState { copy(isLoading = false) } + _viewEvents.post(OnboardingViewEvents.Failure(Throwable("Bad configuration"))) } else { - setState { - copy( - asyncResetPassword = Loading(), - asyncResetMailConfirmed = Uninitialized - ) - } + setState { copy(isLoading = true) } currentJob = viewModelScope.launch { try { safeLoginWizard.resetPassword(action.email, action.newPassword) } catch (failure: Throwable) { - setState { - copy( - asyncResetPassword = Fail(failure) - ) - } + setState { copy(isLoading = false) } + _viewEvents.post(OnboardingViewEvents.Failure(failure)) return@launch } setState { copy( - asyncResetPassword = Success(Unit), + isLoading = false, resetPasswordEmail = action.email ) } @@ -468,34 +441,22 @@ class OnboardingViewModel @AssistedInject constructor( val safeLoginWizard = loginWizard if (safeLoginWizard == null) { - setState { - copy( - asyncResetPassword = Uninitialized, - asyncResetMailConfirmed = Fail(Throwable("Bad configuration")) - ) - } + setState { copy(isLoading = false) } + _viewEvents.post(OnboardingViewEvents.Failure(Throwable("Bad configuration"))) } else { - setState { - copy( - asyncResetPassword = Uninitialized, - asyncResetMailConfirmed = Loading() - ) - } + setState { copy(isLoading = false) } currentJob = viewModelScope.launch { try { safeLoginWizard.resetPasswordMailConfirmed() } catch (failure: Throwable) { - setState { - copy( - asyncResetMailConfirmed = Fail(failure) - ) - } + setState { copy(isLoading = false) } + _viewEvents.post(OnboardingViewEvents.Failure(failure)) return@launch } setState { copy( - asyncResetMailConfirmed = Success(Unit), + isLoading = false, resetPasswordEmail = null ) } @@ -509,135 +470,45 @@ class OnboardingViewModel @AssistedInject constructor( when (state.signMode) { SignMode.Unknown -> error("Developer error, invalid sign mode") SignMode.SignIn -> handleLogin(action) - SignMode.SignUp -> handleRegisterWith(action) + SignMode.SignUp -> handleRegisterWith(OnboardingAction.Register(action.username, action.password, action.initialDeviceName)) SignMode.SignInWithMatrixId -> handleDirectLogin(action, null) - }.exhaustive + } } private fun handleDirectLogin(action: OnboardingAction.LoginOrRegister, homeServerConnectionConfig: HomeServerConnectionConfig?) { - setState { - copy( - asyncLoginAction = Loading() - ) - } - + setState { copy(isLoading = true) } currentJob = viewModelScope.launch { - val data = try { - authenticationService.getWellKnownData(action.username, homeServerConnectionConfig) - } catch (failure: Throwable) { - onDirectLoginError(failure) - return@launch - } - when (data) { - is WellknownResult.Prompt -> - directLoginOnWellknownSuccess(action, data, homeServerConnectionConfig) - is WellknownResult.FailPrompt -> - // Relax on IS discovery if homeserver is valid - if (data.homeServerUrl != null && data.wellKnown != null) { - directLoginOnWellknownSuccess(action, WellknownResult.Prompt(data.homeServerUrl!!, null, data.wellKnown!!), homeServerConnectionConfig) - } else { - onWellKnownError() + directLoginUseCase.execute(action, homeServerConnectionConfig).fold( + onSuccess = { onSessionCreated(it, isAccountCreated = false) }, + onFailure = { + setState { copy(isLoading = false) } + _viewEvents.post(OnboardingViewEvents.Failure(it)) } - else -> { - onWellKnownError() - } - }.exhaustive - } - } - - private fun onWellKnownError() { - setState { - copy( - asyncLoginAction = Uninitialized ) } - _viewEvents.post(OnboardingViewEvents.Failure(Exception(stringProvider.getString(R.string.autodiscover_well_known_error)))) - } - - private suspend fun directLoginOnWellknownSuccess(action: OnboardingAction.LoginOrRegister, - wellKnownPrompt: WellknownResult.Prompt, - homeServerConnectionConfig: HomeServerConnectionConfig?) { - val alteredHomeServerConnectionConfig = homeServerConnectionConfig - ?.copy( - homeServerUriBase = Uri.parse(wellKnownPrompt.homeServerUrl), - identityServerUri = wellKnownPrompt.identityServerUrl?.let { Uri.parse(it) } - ) - ?: HomeServerConnectionConfig( - homeServerUri = Uri.parse("https://${action.username.getDomain()}"), - homeServerUriBase = Uri.parse(wellKnownPrompt.homeServerUrl), - identityServerUri = wellKnownPrompt.identityServerUrl?.let { Uri.parse(it) } - ) - - val data = try { - authenticationService.directAuthentication( - alteredHomeServerConnectionConfig, - action.username, - action.password, - action.initialDeviceName) - } catch (failure: Throwable) { - onDirectLoginError(failure) - return - } - onSessionCreated(data, isAccountCreated = true) - } - - private fun onDirectLoginError(failure: Throwable) { - when (failure) { - is MatrixIdFailure.InvalidMatrixId, - is Failure.UnrecognizedCertificateFailure -> { - // Display this error in a dialog - _viewEvents.post(OnboardingViewEvents.Failure(failure)) - setState { - copy( - asyncLoginAction = Uninitialized - ) - } - } - else -> { - setState { - copy( - asyncLoginAction = Fail(failure) - ) - } - } - } } private fun handleLogin(action: OnboardingAction.LoginOrRegister) { val safeLoginWizard = loginWizard if (safeLoginWizard == null) { - setState { - copy( - asyncLoginAction = Fail(Throwable("Bad configuration")) - ) - } + setState { copy(isLoading = false) } + _viewEvents.post(OnboardingViewEvents.Failure(Throwable("Bad configuration"))) } else { - setState { - copy( - asyncLoginAction = Loading() - ) - } - + setState { copy(isLoading = true) } currentJob = viewModelScope.launch { try { - safeLoginWizard.login( + val result = safeLoginWizard.login( action.username, action.password, action.initialDeviceName ) + reAuthHelper.data = action.password + onSessionCreated(result, isAccountCreated = false) } catch (failure: Throwable) { - setState { - copy( - asyncLoginAction = Fail(failure) - ) - } - null + setState { copy(isLoading = false) } + _viewEvents.post(OnboardingViewEvents.Failure(failure)) } - ?.let { - reAuthHelper.data = action.password - onSessionCreated(it, isAccountCreated = false) - } } } } @@ -649,18 +520,17 @@ class OnboardingViewModel @AssistedInject constructor( _viewEvents.post(OnboardingViewEvents.OnSignModeSelected(SignMode.SignIn)) } - private fun onFlowResponse(flowResult: FlowResult) { + private suspend fun onFlowResponse(flowResult: FlowResult, onNextRegistrationStepAction: (FlowResult) -> Unit) { // If dummy stage is mandatory, and password is already sent, do the dummy stage now if (isRegistrationStarted && flowResult.missingStages.any { it is Stage.Dummy && it.mandatory }) { - handleRegisterDummy() + handleRegisterDummy(onNextRegistrationStepAction) } else { - // Notify the user - _viewEvents.post(OnboardingViewEvents.RegistrationFlowResult(flowResult, isRegistrationStarted)) + onNextRegistrationStepAction(flowResult) } } - private fun handleRegisterDummy() { - handleRegisterAction(RegisterAction.RegisterDummy) + private suspend fun handleRegisterDummy(onNextRegistrationStepAction: (FlowResult) -> Unit) { + internalRegisterAction(RegisterAction.RegisterDummy, onNextRegistrationStepAction) } private suspend fun onSessionCreated(session: Session, isAccountCreated: Boolean) { @@ -678,12 +548,12 @@ class OnboardingViewModel @AssistedInject constructor( true -> { val personalizationState = createPersonalizationState(session, state) setState { - copy(asyncLoginAction = Success(Unit), personalizationState = personalizationState) + copy(isLoading = false, personalizationState = personalizationState) } _viewEvents.post(OnboardingViewEvents.OnAccountCreated) } false -> { - setState { copy(asyncLoginAction = Success(Unit)) } + setState { copy(isLoading = false) } _viewEvents.post(OnboardingViewEvents.OnAccountSignedIn) } } @@ -692,7 +562,7 @@ class OnboardingViewModel @AssistedInject constructor( private suspend fun createPersonalizationState(session: Session, state: OnboardingViewState): PersonalizationState { return when { vectorFeatures.isOnboardingPersonalizeEnabled() -> { - val homeServerCapabilities = session.getHomeServerCapabilities() + val homeServerCapabilities = session.homeServerCapabilitiesService().getHomeServerCapabilities() val capabilityOverrides = vectorOverrides.forceHomeserverCapabilities?.firstOrNull() state.personalizationState.copy( supportsChangingDisplayName = capabilityOverrides?.canChangeDisplayName ?: homeServerCapabilities.canChangeDisplayName, @@ -704,7 +574,7 @@ class OnboardingViewModel @AssistedInject constructor( } private fun handleWebLoginSuccess(action: OnboardingAction.WebLoginSuccess) = withState { state -> - val homeServerConnectionConfigFinal = homeServerConnectionConfigFactory.create(state.homeServerUrl) + val homeServerConnectionConfigFinal = homeServerConnectionConfigFactory.create(state.selectedHomeserver.upstreamUrl) if (homeServerConnectionConfigFinal == null) { // Should not happen @@ -712,97 +582,75 @@ class OnboardingViewModel @AssistedInject constructor( } else { currentJob = viewModelScope.launch { try { - authenticationService.createSessionFromSso(homeServerConnectionConfigFinal, action.credentials) + val result = authenticationService.createSessionFromSso(homeServerConnectionConfigFinal, action.credentials) + onSessionCreated(result, isAccountCreated = false) } catch (failure: Throwable) { - setState { - copy(asyncLoginAction = Fail(failure)) - } - null + setState { copy(isLoading = false) } } - ?.let { onSessionCreated(it, isAccountCreated = false) } } } } - private fun handleUpdateHomeserver(action: OnboardingAction.UpdateHomeServer) { + private fun handleHomeserverChange(action: OnboardingAction.HomeServerChange) { val homeServerConnectionConfig = homeServerConnectionConfigFactory.create(action.homeServerUrl) if (homeServerConnectionConfig == null) { // This is invalid _viewEvents.post(OnboardingViewEvents.Failure(Throwable("Unable to create a HomeServerConnectionConfig"))) } else { - getLoginFlow(homeServerConnectionConfig) + startAuthenticationFlow(action, homeServerConnectionConfig) } } - private fun getLoginFlow(homeServerConnectionConfig: HomeServerConnectionConfig, - serverTypeOverride: ServerType? = null) { + private fun startAuthenticationFlow( + trigger: OnboardingAction?, + homeServerConnectionConfig: HomeServerConnectionConfig, + serverTypeOverride: ServerType? = null + ) { currentHomeServerConnectionConfig = homeServerConnectionConfig currentJob = viewModelScope.launch { - authenticationService.cancelPendingLoginOrRegistration() + setState { copy(isLoading = true) } + runCatching { startAuthenticationFlowUseCase.execute(homeServerConnectionConfig) }.fold( + onSuccess = { onAuthenticationStartedSuccess(trigger, homeServerConnectionConfig, it, serverTypeOverride) }, + onFailure = { _viewEvents.post(OnboardingViewEvents.Failure(it)) } + ) + setState { copy(isLoading = false) } + } + } - setState { - copy( - asyncHomeServerLoginFlowRequest = Loading(), - // If user has entered https://matrix.org, ensure that server type is ServerType.MatrixOrg - // It is also useful to set the value again in the case of a certificate error on matrix.org - serverType = if (homeServerConnectionConfig.homeServerUri.toString() == matrixOrgUrl) { - ServerType.MatrixOrg - } else { - serverTypeOverride ?: serverType - } - ) - } + private suspend fun onAuthenticationStartedSuccess( + trigger: OnboardingAction?, + config: HomeServerConnectionConfig, + authResult: StartAuthenticationResult, + serverTypeOverride: ServerType? + ) { + rememberHomeServer(config.homeServerUri.toString()) + if (authResult.isHomeserverOutdated) { + _viewEvents.post(OnboardingViewEvents.OutdatedHomeserver) + } - val data = try { - authenticationService.getLoginFlow(homeServerConnectionConfig) - } catch (failure: Throwable) { - _viewEvents.post(OnboardingViewEvents.Failure(failure)) - setState { - copy( - asyncHomeServerLoginFlowRequest = Uninitialized, - // If we were trying to retrieve matrix.org login flow, also reset the serverType - serverType = if (serverType == ServerType.MatrixOrg) ServerType.Unknown else serverType - ) + when (trigger) { + is OnboardingAction.HomeServerChange.EditHomeServer -> { + when (awaitState().onboardingFlow) { + OnboardingFlow.SignUp -> internalRegisterAction(RegisterAction.StartRegistration) { _ -> + updateServerSelection(config, serverTypeOverride, authResult) + _viewEvents.post(OnboardingViewEvents.OnHomeserverEdited) + } + else -> throw IllegalArgumentException("developer error") } - null } - - data ?: return@launch - - // Valid Homeserver, add it to the history. - // Note: we add what the user has input, data.homeServerUrlBase can be different - rememberHomeServer(homeServerConnectionConfig.homeServerUri.toString()) - - val loginMode = when { - // SSO login is taken first - data.supportedLoginTypes.contains(LoginFlowTypes.SSO) && - data.supportedLoginTypes.contains(LoginFlowTypes.PASSWORD) -> LoginMode.SsoAndPassword(data.ssoIdentityProviders) - data.supportedLoginTypes.contains(LoginFlowTypes.SSO) -> LoginMode.Sso(data.ssoIdentityProviders) - data.supportedLoginTypes.contains(LoginFlowTypes.PASSWORD) -> LoginMode.Password - else -> LoginMode.Unsupported - } - - setState { - copy( - asyncHomeServerLoginFlowRequest = Uninitialized, - homeServerUrlFromUser = homeServerConnectionConfig.homeServerUri.toString(), - homeServerUrl = data.homeServerUrl, - loginMode = loginMode, - loginModeSupportedTypes = data.supportedLoginTypes.toList() - ) - } - if ((loginMode == LoginMode.Password && !data.isLoginAndRegistrationSupported) || - data.isOutdatedHomeserver) { - // Notify the UI - _viewEvents.post(OnboardingViewEvents.OutdatedHomeserver) - } - - withState { - if (loginMode.supportsSignModeScreen()) { - when (it.onboardingFlow) { - OnboardingFlow.SignIn -> handleUpdateSignMode(OnboardingAction.UpdateSignMode(SignMode.SignIn)) - OnboardingFlow.SignUp -> handleUpdateSignMode(OnboardingAction.UpdateSignMode(SignMode.SignUp)) + is OnboardingAction.HomeServerChange.SelectHomeServer -> { + updateServerSelection(config, serverTypeOverride, authResult) + if (authResult.selectedHomeserver.preferredLoginMode.supportsSignModeScreen()) { + when (awaitState().onboardingFlow) { + OnboardingFlow.SignIn -> { + updateSignMode(SignMode.SignIn) + internalRegisterAction(RegisterAction.StartRegistration, ::emitFlowResultViewEvent) + } + OnboardingFlow.SignUp -> { + updateSignMode(SignMode.SignUp) + internalRegisterAction(RegisterAction.StartRegistration, ::emitFlowResultViewEvent) + } OnboardingFlow.SignInSignUp, null -> { _viewEvents.post(OnboardingViewEvents.OnLoginFlowRetrieved) @@ -812,6 +660,31 @@ class OnboardingViewModel @AssistedInject constructor( _viewEvents.post(OnboardingViewEvents.OnLoginFlowRetrieved) } } + else -> { + updateServerSelection(config, serverTypeOverride, authResult) + _viewEvents.post(OnboardingViewEvents.OnLoginFlowRetrieved) + } + } + } + + private fun updateServerSelection(config: HomeServerConnectionConfig, serverTypeOverride: ServerType?, authResult: StartAuthenticationResult) { + setState { + copy( + serverType = alignServerTypeAfterSubmission(config, serverTypeOverride), + selectedHomeserver = authResult.selectedHomeserver, + ) + } + } + + /** + * If user has entered https://matrix.org, ensure that server type is ServerType.MatrixOrg + * It is also useful to set the value again in the case of a certificate error on matrix.org + **/ + private fun OnboardingViewState.alignServerTypeAfterSubmission(config: HomeServerConnectionConfig, serverTypeOverride: ServerType?): ServerType { + return if (config.homeServerUri.toString() == matrixOrgUrl) { + ServerType.MatrixOrg + } else { + serverTypeOverride ?: serverType } } @@ -828,20 +701,20 @@ class OnboardingViewModel @AssistedInject constructor( } private fun updateDisplayName(displayName: String) { - setState { copy(asyncDisplayName = Loading()) } + setState { copy(isLoading = true) } viewModelScope.launch { val activeSession = activeSessionHolder.getActiveSession() try { - activeSession.setDisplayName(activeSession.myUserId, displayName) + activeSession.profileService().setDisplayName(activeSession.myUserId, displayName) setState { copy( - asyncDisplayName = Success(Unit), + isLoading = false, personalizationState = personalizationState.copy(displayName = displayName) ) } handleDisplayNameStepComplete() } catch (error: Throwable) { - setState { copy(asyncDisplayName = Fail(error)) } + setState { copy(isLoading = false) } _viewEvents.post(OnboardingViewEvents.Failure(error)) } } @@ -883,23 +756,23 @@ class OnboardingViewModel @AssistedInject constructor( when (val pictureUri = state.personalizationState.selectedPictureUri) { null -> _viewEvents.post(OnboardingViewEvents.Failure(NullPointerException("picture uri is missing from state"))) else -> { - setState { copy(asyncProfilePicture = Loading()) } + setState { copy(isLoading = true) } viewModelScope.launch { val activeSession = activeSessionHolder.getActiveSession() try { - activeSession.updateAvatar( + activeSession.profileService().updateAvatar( activeSession.myUserId, pictureUri, uriFilenameResolver.getFilenameFromUri(pictureUri) ?: UUID.randomUUID().toString() ) setState { copy( - asyncProfilePicture = Success(Unit), + isLoading = false, ) } onProfilePictureSaved() } catch (error: Throwable) { - setState { copy(asyncProfilePicture = Fail(error)) } + setState { copy(isLoading = false) } _viewEvents.post(OnboardingViewEvents.Failure(error)) } } diff --git a/vector/src/main/java/im/vector/app/features/onboarding/OnboardingViewState.kt b/vector/src/main/java/im/vector/app/features/onboarding/OnboardingViewState.kt index 8747de6da8..442a0a7df1 100644 --- a/vector/src/main/java/im/vector/app/features/onboarding/OnboardingViewState.kt +++ b/vector/src/main/java/im/vector/app/features/onboarding/OnboardingViewState.kt @@ -18,24 +18,15 @@ package im.vector.app.features.onboarding import android.net.Uri import android.os.Parcelable -import com.airbnb.mvrx.Async -import com.airbnb.mvrx.Loading import com.airbnb.mvrx.MavericksState import com.airbnb.mvrx.PersistState -import com.airbnb.mvrx.Uninitialized import im.vector.app.features.login.LoginMode import im.vector.app.features.login.ServerType import im.vector.app.features.login.SignMode import kotlinx.parcelize.Parcelize data class OnboardingViewState( - val asyncLoginAction: Async = Uninitialized, - val asyncHomeServerLoginFlowRequest: Async = Uninitialized, - val asyncResetPassword: Async = Uninitialized, - val asyncResetMailConfirmed: Async = Uninitialized, - val asyncRegistration: Async = Uninitialized, - val asyncDisplayName: Async = Uninitialized, - val asyncProfilePicture: Async = Uninitialized, + val isLoading: Boolean = false, @PersistState val onboardingFlow: OnboardingFlow? = null, @@ -49,40 +40,20 @@ data class OnboardingViewState( val signMode: SignMode = SignMode.Unknown, @PersistState val resetPasswordEmail: String? = null, - @PersistState - val homeServerUrlFromUser: String? = null, - - // Can be modified after a Wellknown request - @PersistState - val homeServerUrl: String? = null, // For SSO session recovery @PersistState val deviceId: String? = null, - // Network result - @PersistState - val loginMode: LoginMode = LoginMode.Unknown, - // Supported types for the login. We cannot use a sealed class for LoginType because it is not serializable - @PersistState - val loginModeSupportedTypes: List = emptyList(), val knownCustomHomeServersUrls: List = emptyList(), val isForceLoginFallbackEnabled: Boolean = false, @PersistState - val personalizationState: PersonalizationState = PersonalizationState() -) : MavericksState { + val selectedHomeserver: SelectedHomeserverState = SelectedHomeserverState(), - fun isLoading(): Boolean { - return asyncLoginAction is Loading || - asyncHomeServerLoginFlowRequest is Loading || - asyncResetPassword is Loading || - asyncResetMailConfirmed is Loading || - asyncRegistration is Loading || - asyncDisplayName is Loading || - asyncProfilePicture is Loading - } -} + @PersistState + val personalizationState: PersonalizationState = PersonalizationState() +) : MavericksState enum class OnboardingFlow { SignIn, @@ -90,6 +61,15 @@ enum class OnboardingFlow { SignInSignUp } +@Parcelize +data class SelectedHomeserverState( + val description: String? = null, + val userFacingUrl: String? = null, + val upstreamUrl: String? = null, + val preferredLoginMode: LoginMode = LoginMode.Unknown, + val supportedLoginTypes: List = emptyList(), +) : Parcelable + @Parcelize data class PersonalizationState( val supportsChangingDisplayName: Boolean = false, diff --git a/vector/src/main/java/im/vector/app/features/onboarding/StartAuthenticationFlowUseCase.kt b/vector/src/main/java/im/vector/app/features/onboarding/StartAuthenticationFlowUseCase.kt new file mode 100644 index 0000000000..53e1c47fa1 --- /dev/null +++ b/vector/src/main/java/im/vector/app/features/onboarding/StartAuthenticationFlowUseCase.kt @@ -0,0 +1,72 @@ +/* + * Copyright (c) 2022 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package im.vector.app.features.onboarding + +import im.vector.app.R +import im.vector.app.core.extensions.containsAllItems +import im.vector.app.core.resources.StringProvider +import im.vector.app.core.utils.ensureTrailingSlash +import im.vector.app.features.login.LoginMode +import org.matrix.android.sdk.api.auth.AuthenticationService +import org.matrix.android.sdk.api.auth.data.HomeServerConnectionConfig +import org.matrix.android.sdk.api.auth.data.LoginFlowResult +import org.matrix.android.sdk.api.auth.data.LoginFlowTypes +import javax.inject.Inject + +class StartAuthenticationFlowUseCase @Inject constructor( + private val authenticationService: AuthenticationService, + private val stringProvider: StringProvider +) { + + suspend fun execute(config: HomeServerConnectionConfig): StartAuthenticationResult { + authenticationService.cancelPendingLoginOrRegistration() + val authFlow = authenticationService.getLoginFlow(config) + val preferredLoginMode = authFlow.findPreferredLoginMode() + val selection = createSelectedHomeserver(authFlow, config, preferredLoginMode) + val isOutdated = (preferredLoginMode == LoginMode.Password && !authFlow.isLoginAndRegistrationSupported) || authFlow.isOutdatedHomeserver + return StartAuthenticationResult(isOutdated, selection) + } + + private fun createSelectedHomeserver( + authFlow: LoginFlowResult, + config: HomeServerConnectionConfig, + preferredLoginMode: LoginMode + ) = SelectedHomeserverState( + description = when (config.homeServerUri.toString()) { + matrixOrgUrl() -> stringProvider.getString(R.string.ftue_auth_create_account_matrix_dot_org_server_description) + else -> null + }, + userFacingUrl = config.homeServerUri.toString(), + upstreamUrl = authFlow.homeServerUrl, + preferredLoginMode = preferredLoginMode, + supportedLoginTypes = authFlow.supportedLoginTypes + ) + + private fun matrixOrgUrl() = stringProvider.getString(R.string.matrix_org_server_url).ensureTrailingSlash() + + private fun LoginFlowResult.findPreferredLoginMode() = when { + supportedLoginTypes.containsAllItems(LoginFlowTypes.SSO, LoginFlowTypes.PASSWORD) -> LoginMode.SsoAndPassword(ssoIdentityProviders) + supportedLoginTypes.contains(LoginFlowTypes.SSO) -> LoginMode.Sso(ssoIdentityProviders) + supportedLoginTypes.contains(LoginFlowTypes.PASSWORD) -> LoginMode.Password + else -> LoginMode.Unsupported + } + + data class StartAuthenticationResult( + val isHomeserverOutdated: Boolean, + val selectedHomeserver: SelectedHomeserverState + ) +} diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/model/rest/DummyContent.kt b/vector/src/main/java/im/vector/app/features/onboarding/UriFactory.kt similarity index 69% rename from matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/model/rest/DummyContent.kt rename to vector/src/main/java/im/vector/app/features/onboarding/UriFactory.kt index 53d6e4a80a..f9e7a3458c 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/model/rest/DummyContent.kt +++ b/vector/src/main/java/im/vector/app/features/onboarding/UriFactory.kt @@ -1,5 +1,5 @@ /* - * Copyright 2020 The Matrix.org Foundation C.I.C. + * 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. @@ -13,10 +13,15 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.matrix.android.sdk.internal.crypto.model.rest -/** - * Class representing the dummy content - * Ref: https://matrix.org/docs/spec/client_server/latest#id82 - */ -typealias DummyContent = Unit +package im.vector.app.features.onboarding + +import android.net.Uri +import javax.inject.Inject + +class UriFactory @Inject constructor() { + + fun parse(value: String): Uri { + return Uri.parse(value) + } +} diff --git a/vector/src/main/java/im/vector/app/features/onboarding/ftueauth/AbstractFtueAuthFragment.kt b/vector/src/main/java/im/vector/app/features/onboarding/ftueauth/AbstractFtueAuthFragment.kt index 0caf2ea152..64e29766c5 100644 --- a/vector/src/main/java/im/vector/app/features/onboarding/ftueauth/AbstractFtueAuthFragment.kt +++ b/vector/src/main/java/im/vector/app/features/onboarding/ftueauth/AbstractFtueAuthFragment.kt @@ -26,7 +26,6 @@ import com.airbnb.mvrx.withState import com.google.android.material.dialog.MaterialAlertDialogBuilder import im.vector.app.R import im.vector.app.core.dialogs.UnrecognizedCertificateDialog -import im.vector.app.core.extensions.exhaustive import im.vector.app.core.platform.OnBackPressed import im.vector.app.core.platform.VectorBaseFragment import im.vector.app.features.onboarding.OnboardingAction @@ -35,8 +34,6 @@ import im.vector.app.features.onboarding.OnboardingViewModel import im.vector.app.features.onboarding.OnboardingViewState import kotlinx.coroutines.CancellationException import org.matrix.android.sdk.api.failure.Failure -import org.matrix.android.sdk.api.failure.MatrixError -import javax.net.ssl.HttpsURLConnection /** * Parent Fragment for all the login/registration screens @@ -73,7 +70,7 @@ abstract class AbstractFtueAuthFragment : VectorBaseFragment // This is handled by the Activity Unit - }.exhaustive + } } override fun showFailure(throwable: Throwable) { @@ -86,21 +83,8 @@ abstract class AbstractFtueAuthFragment : VectorBaseFragment /* Ignore this error, user has cancelled the action */ Unit - is Failure.ServerError -> - if (throwable.error.code == MatrixError.M_FORBIDDEN && - throwable.httpCode == HttpsURLConnection.HTTP_FORBIDDEN /* 403 */) { - MaterialAlertDialogBuilder(requireActivity()) - .setTitle(R.string.dialog_title_error) - .setMessage(getString(R.string.login_registration_disabled)) - .setPositiveButton(R.string.ok, null) - .show() - } else { - onError(throwable) - } - is Failure.UnrecognizedCertificateFailure -> - showUnrecognizedCertificateFailure(throwable) - else -> - onError(throwable) + is Failure.UnrecognizedCertificateFailure -> showUnrecognizedCertificateFailure(throwable) + else -> onError(throwable) } } diff --git a/vector/src/main/java/im/vector/app/features/onboarding/ftueauth/AbstractSSOFtueAuthFragment.kt b/vector/src/main/java/im/vector/app/features/onboarding/ftueauth/AbstractSSOFtueAuthFragment.kt index 2e9925516c..a032181e4d 100644 --- a/vector/src/main/java/im/vector/app/features/onboarding/ftueauth/AbstractSSOFtueAuthFragment.kt +++ b/vector/src/main/java/im/vector/app/features/onboarding/ftueauth/AbstractSSOFtueAuthFragment.kt @@ -37,7 +37,7 @@ abstract class AbstractSSOFtueAuthFragment : AbstractFtueAuthF override fun onStart() { super.onStart() - val hasSSO = withState(viewModel) { it.loginMode.hasSso() } + val hasSSO = withState(viewModel) { it.selectedHomeserver.preferredLoginMode.hasSso() } if (hasSSO) { val packageName = CustomTabsClient.getPackageName(requireContext(), null) @@ -67,7 +67,7 @@ abstract class AbstractSSOFtueAuthFragment : AbstractFtueAuthF override fun onStop() { super.onStop() - val hasSSO = withState(viewModel) { it.loginMode.hasSso() } + val hasSSO = withState(viewModel) { it.selectedHomeserver.preferredLoginMode.hasSso() } if (hasSSO) { customTabsServiceConnection?.let { requireContext().unbindService(it) } customTabsServiceConnection = null @@ -88,7 +88,7 @@ abstract class AbstractSSOFtueAuthFragment : AbstractFtueAuthF private fun prefetchIfNeeded() { withState(viewModel) { state -> - if (state.loginMode.hasSso() && state.loginMode.ssoIdentityProviders().isNullOrEmpty()) { + if (state.selectedHomeserver.preferredLoginMode.hasSso() && state.selectedHomeserver.preferredLoginMode.ssoIdentityProviders().isNullOrEmpty()) { // in this case we can prefetch (not other cases for privacy concerns) viewModel.getSsoUrl( redirectUrl = SSORedirectRouterActivity.VECTOR_REDIRECT_URL, diff --git a/vector/src/main/java/im/vector/app/features/onboarding/ftueauth/CaptchaWebview.kt b/vector/src/main/java/im/vector/app/features/onboarding/ftueauth/CaptchaWebview.kt new file mode 100644 index 0000000000..558c26fe56 --- /dev/null +++ b/vector/src/main/java/im/vector/app/features/onboarding/ftueauth/CaptchaWebview.kt @@ -0,0 +1,160 @@ +/* + * Copyright (c) 2022 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package im.vector.app.features.onboarding.ftueauth + +import android.annotation.SuppressLint +import android.content.DialogInterface +import android.graphics.Bitmap +import android.net.http.SslError +import android.os.Build +import android.view.KeyEvent +import android.view.View +import android.webkit.SslErrorHandler +import android.webkit.WebResourceRequest +import android.webkit.WebResourceResponse +import android.webkit.WebView +import android.webkit.WebViewClient +import androidx.core.view.isVisible +import androidx.fragment.app.Fragment +import com.google.android.material.dialog.MaterialAlertDialogBuilder +import im.vector.app.R +import im.vector.app.core.utils.AssetReader +import im.vector.app.features.login.JavascriptResponse +import im.vector.app.features.onboarding.OnboardingViewState +import org.matrix.android.sdk.api.util.MatrixJsonParser +import timber.log.Timber +import java.net.URLDecoder +import java.util.Formatter +import javax.inject.Inject + +class CaptchaWebview @Inject constructor( + private val assetReader: AssetReader +) { + + @SuppressLint("SetJavaScriptEnabled") + fun setupWebView( + container: Fragment, + webView: WebView, + progressView: View, + siteKey: String, + state: OnboardingViewState, + onSuccess: (String) -> Unit + ) { + webView.settings.javaScriptEnabled = true + + val reCaptchaPage = assetReader.readAssetFile("reCaptchaPage.html") ?: error("missing asset reCaptchaPage.html") + + val html = Formatter().format(reCaptchaPage, siteKey).toString() + val mime = "text/html" + val encoding = "utf-8" + + val homeServerUrl = state.selectedHomeserver.upstreamUrl ?: error("missing url of homeserver") + webView.loadDataWithBaseURL(homeServerUrl, html, mime, encoding, null) + webView.requestLayout() + + webView.webViewClient = object : WebViewClient() { + override fun onPageStarted(view: WebView?, url: String?, favicon: Bitmap?) { + super.onPageStarted(view, url, favicon) + if (container.isAdded) { + progressView.isVisible = true + } + } + + override fun onPageFinished(view: WebView, url: String) { + super.onPageFinished(view, url) + if (container.isAdded) { + progressView.isVisible = false + } + } + + override fun onReceivedSslError(view: WebView, handler: SslErrorHandler, error: SslError) { + Timber.d("## onReceivedSslError() : ${error.certificate}") + if (container.isAdded) { + showSslErrorDialog(container, handler) + } + } + + private fun onError(errorMessage: String) { + Timber.e("## onError() : $errorMessage") + } + + @SuppressLint("NewApi") + override fun onReceivedHttpError(view: WebView, request: WebResourceRequest, errorResponse: WebResourceResponse) { + super.onReceivedHttpError(view, request, errorResponse) + when { + request.url.toString().endsWith("favicon.ico") -> { + // ignore favicon errors + } + else -> onError(errorResponse.toText()) + } + } + + override fun onReceivedError(view: WebView, errorCode: Int, description: String, failingUrl: String) { + @Suppress("DEPRECATION") + super.onReceivedError(view, errorCode, description, failingUrl) + onError(description) + } + + override fun shouldOverrideUrlLoading(view: WebView?, url: String?): Boolean { + if (url?.startsWith("js:") == true) { + val javascriptResponse = parseJsonFromUrl(url) + val response = javascriptResponse?.response + if (javascriptResponse?.action == "verifyCallback" && response != null) { + onSuccess(response) + } + } + return true + } + + private fun parseJsonFromUrl(url: String): JavascriptResponse? { + return try { + val json = URLDecoder.decode(url.substringAfter("js:"), "UTF-8") + MatrixJsonParser.getMoshi().adapter(JavascriptResponse::class.java).fromJson(json) + } catch (e: Exception) { + Timber.e(e, "## shouldOverrideUrlLoading(): failed") + null + } + } + } + } + + private fun showSslErrorDialog(container: Fragment, handler: SslErrorHandler) { + MaterialAlertDialogBuilder(container.requireActivity()) + .setMessage(R.string.ssl_could_not_verify) + .setPositiveButton(R.string.ssl_trust) { _, _ -> + Timber.d("## onReceivedSslError() : the user trusted") + handler.proceed() + } + .setNegativeButton(R.string.ssl_do_not_trust) { _, _ -> + Timber.d("## onReceivedSslError() : the user did not trust") + handler.cancel() + } + .setOnKeyListener(DialogInterface.OnKeyListener { dialog, keyCode, event -> + if (event.action == KeyEvent.ACTION_UP && keyCode == KeyEvent.KEYCODE_BACK) { + handler.cancel() + Timber.d("## onReceivedSslError() : the user dismisses the trust dialog.") + dialog.dismiss() + return@OnKeyListener true + } + false + }) + .setCancelable(false) + .show() + } +} + +private fun WebResourceResponse.toText() = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) reasonPhrase else toString() diff --git a/vector/src/main/java/im/vector/app/features/onboarding/ftueauth/FtueAuthCaptchaFragment.kt b/vector/src/main/java/im/vector/app/features/onboarding/ftueauth/FtueAuthCaptchaFragment.kt index 4773332138..a3665a8f40 100644 --- a/vector/src/main/java/im/vector/app/features/onboarding/ftueauth/FtueAuthCaptchaFragment.kt +++ b/vector/src/main/java/im/vector/app/features/onboarding/ftueauth/FtueAuthCaptchaFragment.kt @@ -1,5 +1,5 @@ /* - * Copyright 2019 New Vector Ltd + * Copyright (c) 2022 New Vector Ltd * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -16,35 +16,15 @@ package im.vector.app.features.onboarding.ftueauth -import android.annotation.SuppressLint -import android.content.DialogInterface -import android.graphics.Bitmap -import android.net.http.SslError -import android.os.Build import android.os.Parcelable -import android.view.KeyEvent import android.view.LayoutInflater import android.view.ViewGroup -import android.webkit.SslErrorHandler -import android.webkit.WebResourceRequest -import android.webkit.WebResourceResponse -import android.webkit.WebView -import android.webkit.WebViewClient -import androidx.core.view.isVisible import com.airbnb.mvrx.args -import com.google.android.material.dialog.MaterialAlertDialogBuilder -import im.vector.app.R -import im.vector.app.core.utils.AssetReader -import im.vector.app.databinding.FragmentLoginCaptchaBinding -import im.vector.app.features.login.JavascriptResponse +import im.vector.app.databinding.FragmentFtueLoginCaptchaBinding import im.vector.app.features.onboarding.OnboardingAction import im.vector.app.features.onboarding.OnboardingViewState import im.vector.app.features.onboarding.RegisterAction import kotlinx.parcelize.Parcelize -import org.matrix.android.sdk.internal.di.MoshiProvider -import timber.log.Timber -import java.net.URLDecoder -import java.util.Formatter import javax.inject.Inject @Parcelize @@ -53,150 +33,28 @@ data class FtueAuthCaptchaFragmentArgument( ) : Parcelable /** - * In this screen, the user is asked to confirm he is not a robot + * In this screen, the user is asked to confirm they are not a robot */ class FtueAuthCaptchaFragment @Inject constructor( - private val assetReader: AssetReader -) : AbstractFtueAuthFragment() { - - override fun getBinding(inflater: LayoutInflater, container: ViewGroup?): FragmentLoginCaptchaBinding { - return FragmentLoginCaptchaBinding.inflate(inflater, container, false) - } + private val captchaWebview: CaptchaWebview +) : AbstractFtueAuthFragment() { private val params: FtueAuthCaptchaFragmentArgument by args() - private var isWebViewLoaded = false - @SuppressLint("SetJavaScriptEnabled") - private fun setupWebView(state: OnboardingViewState) { - views.loginCaptchaWevView.settings.javaScriptEnabled = true - - val reCaptchaPage = assetReader.readAssetFile("reCaptchaPage.html") ?: error("missing asset reCaptchaPage.html") - - val html = Formatter().format(reCaptchaPage, params.siteKey).toString() - val mime = "text/html" - val encoding = "utf-8" - - val homeServerUrl = state.homeServerUrl ?: error("missing url of homeserver") - views.loginCaptchaWevView.loadDataWithBaseURL(homeServerUrl, html, mime, encoding, null) - views.loginCaptchaWevView.requestLayout() - - views.loginCaptchaWevView.webViewClient = object : WebViewClient() { - override fun onPageStarted(view: WebView?, url: String?, favicon: Bitmap?) { - super.onPageStarted(view, url, favicon) - - if (!isAdded) { - return - } - - // Show loader - views.loginCaptchaProgress.isVisible = true - } - - override fun onPageFinished(view: WebView, url: String) { - super.onPageFinished(view, url) - - if (!isAdded) { - return - } - - // Hide loader - views.loginCaptchaProgress.isVisible = false - } - - override fun onReceivedSslError(view: WebView, handler: SslErrorHandler, error: SslError) { - Timber.d("## onReceivedSslError() : ${error.certificate}") - - if (!isAdded) { - return - } - - MaterialAlertDialogBuilder(requireActivity()) - .setMessage(R.string.ssl_could_not_verify) - .setPositiveButton(R.string.ssl_trust) { _, _ -> - Timber.d("## onReceivedSslError() : the user trusted") - handler.proceed() - } - .setNegativeButton(R.string.ssl_do_not_trust) { _, _ -> - Timber.d("## onReceivedSslError() : the user did not trust") - handler.cancel() - } - .setOnKeyListener(DialogInterface.OnKeyListener { dialog, keyCode, event -> - if (event.action == KeyEvent.ACTION_UP && keyCode == KeyEvent.KEYCODE_BACK) { - handler.cancel() - Timber.d("## onReceivedSslError() : the user dismisses the trust dialog.") - dialog.dismiss() - return@OnKeyListener true - } - false - }) - .setCancelable(false) - .show() - } - - // common error message - private fun onError(errorMessage: String) { - Timber.e("## onError() : $errorMessage") - - // TODO - // Toast.makeText(this@AccountCreationCaptchaActivity, errorMessage, Toast.LENGTH_LONG).show() - - // on error case, close this activity - // runOnUiThread(Runnable { finish() }) - } - - @SuppressLint("NewApi") - override fun onReceivedHttpError(view: WebView, request: WebResourceRequest, errorResponse: WebResourceResponse) { - super.onReceivedHttpError(view, request, errorResponse) - - if (request.url.toString().endsWith("favicon.ico")) { - // Ignore this error - return - } - - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { - onError(errorResponse.reasonPhrase) - } else { - onError(errorResponse.toString()) - } - } - - override fun onReceivedError(view: WebView, errorCode: Int, description: String, failingUrl: String) { - @Suppress("DEPRECATION") - super.onReceivedError(view, errorCode, description, failingUrl) - onError(description) - } - - override fun shouldOverrideUrlLoading(view: WebView?, url: String?): Boolean { - if (url?.startsWith("js:") == true) { - var json = url.substring(3) - var javascriptResponse: JavascriptResponse? = null - - try { - // URL decode - json = URLDecoder.decode(json, "UTF-8") - javascriptResponse = MoshiProvider.providesMoshi().adapter(JavascriptResponse::class.java).fromJson(json) - } catch (e: Exception) { - Timber.e(e, "## shouldOverrideUrlLoading(): failed") - } - - val response = javascriptResponse?.response - if (javascriptResponse?.action == "verifyCallback" && response != null) { - viewModel.handle(OnboardingAction.PostRegisterAction(RegisterAction.CaptchaDone(response))) - } - } - return true - } - } + override fun getBinding(inflater: LayoutInflater, container: ViewGroup?): FragmentFtueLoginCaptchaBinding { + return FragmentFtueLoginCaptchaBinding.inflate(inflater, container, false) } override fun resetViewModel() { - viewModel.handle(OnboardingAction.ResetLogin) + viewModel.handle(OnboardingAction.ResetAuthenticationAttempt) } override fun updateWithState(state: OnboardingViewState) { if (!isWebViewLoaded) { - setupWebView(state) + captchaWebview.setupWebView(this, views.loginCaptchaWevView, views.loginCaptchaProgress, params.siteKey, state) { + viewModel.handle(OnboardingAction.PostRegisterAction(RegisterAction.CaptchaDone(it))) + } isWebViewLoaded = true } } diff --git a/vector/src/main/java/im/vector/app/features/onboarding/ftueauth/FtueAuthCombinedRegisterFragment.kt b/vector/src/main/java/im/vector/app/features/onboarding/ftueauth/FtueAuthCombinedRegisterFragment.kt new file mode 100644 index 0000000000..0755f18c8c --- /dev/null +++ b/vector/src/main/java/im/vector/app/features/onboarding/ftueauth/FtueAuthCombinedRegisterFragment.kt @@ -0,0 +1,212 @@ +/* + * Copyright 2019 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package im.vector.app.features.onboarding.ftueauth + +import android.os.Build +import android.os.Bundle +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import android.view.inputmethod.EditorInfo +import androidx.autofill.HintConstants +import androidx.core.text.isDigitsOnly +import androidx.core.view.isVisible +import androidx.lifecycle.lifecycleScope +import com.airbnb.mvrx.withState +import com.google.android.material.dialog.MaterialAlertDialogBuilder +import im.vector.app.R +import im.vector.app.core.extensions.content +import im.vector.app.core.extensions.editText +import im.vector.app.core.extensions.hasContentFlow +import im.vector.app.core.extensions.hasSurroundingSpaces +import im.vector.app.core.extensions.hideKeyboard +import im.vector.app.core.extensions.hidePassword +import im.vector.app.core.extensions.realignPercentagesToParent +import im.vector.app.core.extensions.toReducedUrl +import im.vector.app.databinding.FragmentFtueCombinedRegisterBinding +import im.vector.app.features.login.LoginMode +import im.vector.app.features.login.SSORedirectRouterActivity +import im.vector.app.features.login.SocialLoginButtonsView +import im.vector.app.features.onboarding.OnboardingAction +import im.vector.app.features.onboarding.OnboardingViewEvents +import im.vector.app.features.onboarding.OnboardingViewState +import kotlinx.coroutines.flow.combine +import kotlinx.coroutines.flow.launchIn +import kotlinx.coroutines.flow.onEach +import org.matrix.android.sdk.api.auth.data.SsoIdentityProvider +import org.matrix.android.sdk.api.failure.isInvalidPassword +import org.matrix.android.sdk.api.failure.isInvalidUsername +import org.matrix.android.sdk.api.failure.isLoginEmailUnknown +import org.matrix.android.sdk.api.failure.isRegistrationDisabled +import org.matrix.android.sdk.api.failure.isUsernameInUse +import org.matrix.android.sdk.api.failure.isWeakPassword +import javax.inject.Inject + +class FtueAuthCombinedRegisterFragment @Inject constructor() : AbstractSSOFtueAuthFragment() { + + override fun getBinding(inflater: LayoutInflater, container: ViewGroup?): FragmentFtueCombinedRegisterBinding { + return FragmentFtueCombinedRegisterBinding.inflate(inflater, container, false) + } + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + setupSubmitButton() + views.createAccountRoot.realignPercentagesToParent() + views.editServerButton.debouncedClicks { + viewModel.handle(OnboardingAction.PostViewEvent(OnboardingViewEvents.EditServerSelection)) + } + + views.createAccountPasswordInput.editText().setOnEditorActionListener { _, actionId, _ -> + if (actionId == EditorInfo.IME_ACTION_DONE) { + submit() + return@setOnEditorActionListener true + } + return@setOnEditorActionListener false + } + } + + private fun setupSubmitButton() { + views.createAccountSubmit.setOnClickListener { submit() } + observeInputFields() + .onEach { + views.createAccountPasswordInput.error = null + views.createAccountInput.error = null + views.createAccountSubmit.isEnabled = it + } + .launchIn(viewLifecycleOwner.lifecycleScope) + } + + private fun observeInputFields() = combine( + views.createAccountInput.hasContentFlow { it.trim() }, + views.createAccountPasswordInput.hasContentFlow(), + transform = { isLoginNotEmpty, isPasswordNotEmpty -> isLoginNotEmpty && isPasswordNotEmpty } + ) + + private fun submit() { + withState(viewModel) { state -> + cleanupUi() + + val login = views.createAccountInput.content() + val password = views.createAccountPasswordInput.content() + + // This can be called by the IME action, so deal with empty cases + var error = 0 + if (login.isEmpty()) { + views.createAccountInput.error = getString(R.string.error_empty_field_choose_user_name) + error++ + } + if (state.isNumericOnlyUserIdForbidden() && login.isDigitsOnly()) { + views.createAccountInput.error = getString(R.string.error_forbidden_digits_only_username) + error++ + } + if (password.isEmpty()) { + views.createAccountPasswordInput.error = getString(R.string.error_empty_field_choose_password) + error++ + } + + if (error == 0) { + viewModel.handle(OnboardingAction.Register(login, password, getString(R.string.login_default_session_public_name))) + } + } + } + + private fun cleanupUi() { + views.createAccountSubmit.hideKeyboard() + views.createAccountInput.error = null + views.createAccountPasswordInput.error = null + } + + override fun resetViewModel() { + viewModel.handle(OnboardingAction.ResetAuthenticationAttempt) + } + + override fun onError(throwable: Throwable) { + // Trick to display the error without text. + views.createAccountInput.error = " " + when { + throwable.isUsernameInUse() || throwable.isInvalidUsername() -> { + views.createAccountInput.error = errorFormatter.toHumanReadable(throwable) + } + throwable.isLoginEmailUnknown() -> { + views.createAccountInput.error = getString(R.string.login_login_with_email_error) + } + throwable.isInvalidPassword() && views.createAccountPasswordInput.hasSurroundingSpaces() -> { + views.createAccountPasswordInput.error = getString(R.string.auth_invalid_login_param_space_in_password) + } + throwable.isWeakPassword() || throwable.isInvalidPassword() -> { + views.createAccountPasswordInput.error = errorFormatter.toHumanReadable(throwable) + } + throwable.isRegistrationDisabled() -> { + MaterialAlertDialogBuilder(requireActivity()) + .setTitle(R.string.dialog_title_error) + .setMessage(getString(R.string.login_registration_disabled)) + .setPositiveButton(R.string.ok, null) + .show() + } + else -> { + super.onError(throwable) + } + } + } + + override fun updateWithState(state: OnboardingViewState) { + setupUi(state) + setupAutoFill() + + views.selectedServerName.text = state.selectedHomeserver.userFacingUrl.toReducedUrl() + views.selectedServerDescription.text = state.selectedHomeserver.description + + if (state.isLoading) { + // Ensure password is hidden + views.createAccountPasswordInput.editText().hidePassword() + } + } + + private fun setupUi(state: OnboardingViewState) { + when (state.selectedHomeserver.preferredLoginMode) { + is LoginMode.SsoAndPassword -> renderSsoProviders(state.deviceId, state.selectedHomeserver.preferredLoginMode.ssoIdentityProviders) + else -> hideSsoProviders() + } + } + + private fun renderSsoProviders(deviceId: String?, ssoProviders: List?) { + views.ssoGroup.isVisible = ssoProviders?.isNotEmpty() == true + views.ssoButtons.mode = SocialLoginButtonsView.Mode.MODE_CONTINUE + views.ssoButtons.ssoIdentityProviders = ssoProviders?.sorted() + views.ssoButtons.listener = SocialLoginButtonsView.InteractionListener { id -> + viewModel.getSsoUrl( + redirectUrl = SSORedirectRouterActivity.VECTOR_REDIRECT_URL, + deviceId = deviceId, + providerId = id + )?.let { openInCustomTab(it) } + } + } + + private fun hideSsoProviders() { + views.ssoGroup.isVisible = false + views.ssoButtons.ssoIdentityProviders = null + } + + private fun setupAutoFill() { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { + views.createAccountInput.setAutofillHints(HintConstants.AUTOFILL_HINT_NEW_USERNAME) + views.createAccountPasswordInput.setAutofillHints(HintConstants.AUTOFILL_HINT_NEW_PASSWORD) + } + } + + private fun OnboardingViewState.isNumericOnlyUserIdForbidden() = selectedHomeserver.userFacingUrl == getString(R.string.matrix_org_server_url) +} diff --git a/vector/src/main/java/im/vector/app/features/onboarding/ftueauth/FtueAuthCombinedServerSelectionFragment.kt b/vector/src/main/java/im/vector/app/features/onboarding/ftueauth/FtueAuthCombinedServerSelectionFragment.kt new file mode 100644 index 0000000000..2e6057288a --- /dev/null +++ b/vector/src/main/java/im/vector/app/features/onboarding/ftueauth/FtueAuthCombinedServerSelectionFragment.kt @@ -0,0 +1,97 @@ +/* + * Copyright (c) 2022 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package im.vector.app.features.onboarding.ftueauth + +import android.os.Bundle +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import android.view.inputmethod.EditorInfo +import androidx.lifecycle.lifecycleScope +import im.vector.app.R +import im.vector.app.core.extensions.content +import im.vector.app.core.extensions.editText +import im.vector.app.core.extensions.realignPercentagesToParent +import im.vector.app.core.extensions.toReducedUrl +import im.vector.app.core.utils.ensureProtocol +import im.vector.app.core.utils.ensureTrailingSlash +import im.vector.app.core.utils.openUrlInExternalBrowser +import im.vector.app.databinding.FragmentFtueServerSelectionCombinedBinding +import im.vector.app.features.onboarding.OnboardingAction +import im.vector.app.features.onboarding.OnboardingViewEvents +import im.vector.app.features.onboarding.OnboardingViewState +import kotlinx.coroutines.flow.launchIn +import kotlinx.coroutines.flow.onEach +import org.matrix.android.sdk.api.failure.isHomeserverUnavailable +import reactivecircus.flowbinding.android.widget.textChanges +import javax.inject.Inject + +class FtueAuthCombinedServerSelectionFragment @Inject constructor() : AbstractFtueAuthFragment() { + + override fun getBinding(inflater: LayoutInflater, container: ViewGroup?): FragmentFtueServerSelectionCombinedBinding { + return FragmentFtueServerSelectionCombinedBinding.inflate(inflater, container, false) + } + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + setupViews() + } + + private fun setupViews() { + views.chooseServerRoot.realignPercentagesToParent() + views.chooseServerToolbar.setNavigationOnClickListener { + viewModel.handle(OnboardingAction.PostViewEvent(OnboardingViewEvents.OnBack)) + } + views.chooseServerInput.editText?.setOnEditorActionListener { _, actionId, _ -> + when (actionId) { + EditorInfo.IME_ACTION_DONE -> { + updateServerUrl() + } + } + false + } + views.chooseServerGetInTouch.debouncedClicks { openUrlInExternalBrowser(requireContext(), getString(R.string.ftue_ems_url)) } + views.chooseServerSubmit.debouncedClicks { updateServerUrl() } + views.chooseServerInput.editText().textChanges() + .onEach { views.chooseServerInput.error = null } + .launchIn(lifecycleScope) + } + + private fun updateServerUrl() { + viewModel.handle(OnboardingAction.HomeServerChange.EditHomeServer(views.chooseServerInput.content().ensureProtocol().ensureTrailingSlash())) + } + + override fun resetViewModel() { + // do nothing + } + + override fun updateWithState(state: OnboardingViewState) { + if (views.chooseServerInput.content().isEmpty()) { + val userUrlInput = state.selectedHomeserver.userFacingUrl?.toReducedUrlKeepingSchemaIfInsecure() + views.chooseServerInput.editText().setText(userUrlInput) + } + } + + override fun onError(throwable: Throwable) { + views.chooseServerInput.error = when { + throwable.isHomeserverUnavailable() -> getString(R.string.login_error_homeserver_not_found) + else -> errorFormatter.toHumanReadable(throwable) + } + } + + private fun String.toReducedUrlKeepingSchemaIfInsecure() = toReducedUrl(keepSchema = this.startsWith("http://")) +} diff --git a/vector/src/main/java/im/vector/app/features/onboarding/ftueauth/FtueAuthGenericTextInputFormFragment.kt b/vector/src/main/java/im/vector/app/features/onboarding/ftueauth/FtueAuthGenericTextInputFormFragment.kt index 2800530152..466e141fdf 100644 --- a/vector/src/main/java/im/vector/app/features/onboarding/ftueauth/FtueAuthGenericTextInputFormFragment.kt +++ b/vector/src/main/java/im/vector/app/features/onboarding/ftueauth/FtueAuthGenericTextInputFormFragment.kt @@ -254,6 +254,6 @@ class FtueAuthGenericTextInputFormFragment @Inject constructor() : AbstractFtueA } override fun resetViewModel() { - viewModel.handle(OnboardingAction.ResetLogin) + viewModel.handle(OnboardingAction.ResetAuthenticationAttempt) } } diff --git a/vector/src/main/java/im/vector/app/features/onboarding/ftueauth/FtueAuthLegacyStyleCaptchaFragment.kt b/vector/src/main/java/im/vector/app/features/onboarding/ftueauth/FtueAuthLegacyStyleCaptchaFragment.kt new file mode 100644 index 0000000000..2accab00e0 --- /dev/null +++ b/vector/src/main/java/im/vector/app/features/onboarding/ftueauth/FtueAuthLegacyStyleCaptchaFragment.kt @@ -0,0 +1,61 @@ +/* + * Copyright 2022 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package im.vector.app.features.onboarding.ftueauth + +import android.os.Parcelable +import android.view.LayoutInflater +import android.view.ViewGroup +import com.airbnb.mvrx.args +import im.vector.app.databinding.FragmentLoginCaptchaBinding +import im.vector.app.features.onboarding.OnboardingAction +import im.vector.app.features.onboarding.OnboardingViewState +import im.vector.app.features.onboarding.RegisterAction +import kotlinx.parcelize.Parcelize +import javax.inject.Inject + +@Parcelize +data class FtueAuthLegacyStyleCaptchaFragmentArgument( + val siteKey: String +) : Parcelable + +/** + * In this screen, the user is asked to confirm they are not a robot + */ +class FtueAuthLegacyStyleCaptchaFragment @Inject constructor( + private val captchaWebview: CaptchaWebview +) : AbstractFtueAuthFragment() { + + private val params: FtueAuthLegacyStyleCaptchaFragmentArgument by args() + private var isWebViewLoaded = false + + override fun getBinding(inflater: LayoutInflater, container: ViewGroup?): FragmentLoginCaptchaBinding { + return FragmentLoginCaptchaBinding.inflate(inflater, container, false) + } + + override fun resetViewModel() { + viewModel.handle(OnboardingAction.ResetAuthenticationAttempt) + } + + override fun updateWithState(state: OnboardingViewState) { + if (!isWebViewLoaded) { + captchaWebview.setupWebView(this, views.loginCaptchaWevView, views.loginCaptchaProgress, params.siteKey, state) { + viewModel.handle(OnboardingAction.PostRegisterAction(RegisterAction.CaptchaDone(it))) + } + isWebViewLoaded = true + } + } +} diff --git a/vector/src/main/java/im/vector/app/features/onboarding/ftueauth/FtueAuthLoginFragment.kt b/vector/src/main/java/im/vector/app/features/onboarding/ftueauth/FtueAuthLoginFragment.kt index 5f15d9a35d..4888b43946 100644 --- a/vector/src/main/java/im/vector/app/features/onboarding/ftueauth/FtueAuthLoginFragment.kt +++ b/vector/src/main/java/im/vector/app/features/onboarding/ftueauth/FtueAuthLoginFragment.kt @@ -26,11 +26,8 @@ import androidx.autofill.HintConstants import androidx.core.text.isDigitsOnly import androidx.core.view.isVisible import androidx.lifecycle.lifecycleScope -import com.airbnb.mvrx.Fail -import com.airbnb.mvrx.Loading -import com.airbnb.mvrx.Success +import com.google.android.material.dialog.MaterialAlertDialogBuilder import im.vector.app.R -import im.vector.app.core.extensions.exhaustive import im.vector.app.core.extensions.hideKeyboard import im.vector.app.core.extensions.hidePassword import im.vector.app.core.extensions.toReducedUrl @@ -47,9 +44,12 @@ import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.onEach -import org.matrix.android.sdk.api.failure.Failure -import org.matrix.android.sdk.api.failure.MatrixError import org.matrix.android.sdk.api.failure.isInvalidPassword +import org.matrix.android.sdk.api.failure.isInvalidUsername +import org.matrix.android.sdk.api.failure.isLoginEmailUnknown +import org.matrix.android.sdk.api.failure.isRegistrationDisabled +import org.matrix.android.sdk.api.failure.isUsernameInUse +import org.matrix.android.sdk.api.failure.isWeakPassword import reactivecircus.flowbinding.android.widget.textChanges import javax.inject.Inject @@ -105,7 +105,7 @@ class FtueAuthLoginFragment @Inject constructor() : AbstractSSOFtueAuthFragment< views.loginField.setAutofillHints(HintConstants.AUTOFILL_HINT_USERNAME) views.passwordField.setAutofillHints(HintConstants.AUTOFILL_HINT_PASSWORD) } - }.exhaustive + } } } @@ -115,7 +115,7 @@ class FtueAuthLoginFragment @Inject constructor() : AbstractSSOFtueAuthFragment< SignMode.SignUp -> SocialLoginButtonsView.Mode.MODE_SIGN_UP SignMode.SignIn, SignMode.SignInWithMatrixId -> SocialLoginButtonsView.Mode.MODE_SIGN_IN - }.exhaustive + } } private fun submit() { @@ -135,7 +135,7 @@ class FtueAuthLoginFragment @Inject constructor() : AbstractSSOFtueAuthFragment< error++ } if (isSignupMode && isNumericOnlyUserIdForbidden && login.isDigitsOnly()) { - views.loginFieldTil.error = "The homeserver does not accept username with only digits." + views.loginFieldTil.error = getString(R.string.error_forbidden_digits_only_username) error++ } if (password.isEmpty()) { @@ -184,7 +184,7 @@ class FtueAuthLoginFragment @Inject constructor() : AbstractSSOFtueAuthFragment< ServerType.MatrixOrg -> { views.loginServerIcon.isVisible = true views.loginServerIcon.setImageResource(R.drawable.ic_logo_matrix_org) - views.loginTitle.text = getString(resId, state.homeServerUrlFromUser.toReducedUrl()) + views.loginTitle.text = getString(resId, state.selectedHomeserver.userFacingUrl.toReducedUrl()) views.loginNotice.text = getString(R.string.login_server_matrix_org_text) } ServerType.EMS -> { @@ -195,16 +195,16 @@ class FtueAuthLoginFragment @Inject constructor() : AbstractSSOFtueAuthFragment< } ServerType.Other -> { views.loginServerIcon.isVisible = false - views.loginTitle.text = getString(resId, state.homeServerUrlFromUser.toReducedUrl()) + views.loginTitle.text = getString(resId, state.selectedHomeserver.userFacingUrl.toReducedUrl()) views.loginNotice.text = getString(R.string.login_server_other_text) } ServerType.Unknown -> Unit /* Should not happen */ } views.loginPasswordNotice.isVisible = false - if (state.loginMode is LoginMode.SsoAndPassword) { + if (state.selectedHomeserver.preferredLoginMode is LoginMode.SsoAndPassword) { views.loginSocialLoginContainer.isVisible = true - views.loginSocialLoginButtons.ssoIdentityProviders = state.loginMode.ssoIdentityProviders?.sorted() + views.loginSocialLoginButtons.ssoIdentityProviders = state.selectedHomeserver.preferredLoginMode.ssoIdentityProviders?.sorted() views.loginSocialLoginButtons.listener = object : SocialLoginButtonsView.InteractionListener { override fun onProviderSelected(id: String?) { viewModel.getSsoUrl( @@ -254,16 +254,35 @@ class FtueAuthLoginFragment @Inject constructor() : AbstractSSOFtueAuthFragment< } override fun resetViewModel() { - viewModel.handle(OnboardingAction.ResetLogin) + viewModel.handle(OnboardingAction.ResetAuthenticationAttempt) } override fun onError(throwable: Throwable) { - // Show M_WEAK_PASSWORD error in the password field - if (throwable is Failure.ServerError && - throwable.error.code == MatrixError.M_WEAK_PASSWORD) { - views.passwordFieldTil.error = errorFormatter.toHumanReadable(throwable) - } else { - views.loginFieldTil.error = errorFormatter.toHumanReadable(throwable) + // Trick to display the error without text. + views.loginFieldTil.error = " " + when { + throwable.isUsernameInUse() || throwable.isInvalidUsername() -> { + views.loginFieldTil.error = errorFormatter.toHumanReadable(throwable) + } + throwable.isLoginEmailUnknown() -> { + views.loginFieldTil.error = getString(R.string.login_login_with_email_error) + } + throwable.isInvalidPassword() && spaceInPassword() -> { + views.passwordFieldTil.error = getString(R.string.auth_invalid_login_param_space_in_password) + } + throwable.isWeakPassword() || throwable.isInvalidPassword() -> { + views.passwordFieldTil.error = errorFormatter.toHumanReadable(throwable) + } + throwable.isRegistrationDisabled() -> { + MaterialAlertDialogBuilder(requireActivity()) + .setTitle(R.string.dialog_title_error) + .setMessage(getString(R.string.login_registration_disabled)) + .setPositiveButton(R.string.ok, null) + .show() + } + else -> { + super.onError(throwable) + } } } @@ -276,39 +295,9 @@ class FtueAuthLoginFragment @Inject constructor() : AbstractSSOFtueAuthFragment< setupSocialLoginButtons(state) setupButtons(state) - when (state.asyncLoginAction) { - is Loading -> { - // Ensure password is hidden - views.passwordField.hidePassword() - } - is Fail -> { - val error = state.asyncLoginAction.error - if (error is Failure.ServerError && - error.error.code == MatrixError.M_FORBIDDEN && - error.error.message.isEmpty()) { - // Login with email, but email unknown - views.loginFieldTil.error = getString(R.string.login_login_with_email_error) - } else { - // Trick to display the error without text. - views.loginFieldTil.error = " " - if (error.isInvalidPassword() && spaceInPassword()) { - views.passwordFieldTil.error = getString(R.string.auth_invalid_login_param_space_in_password) - } else { - views.passwordFieldTil.error = errorFormatter.toHumanReadable(error) - } - } - } - // Success is handled by the LoginActivity - is Success -> Unit - } - - when (state.asyncRegistration) { - is Loading -> { - // Ensure password is hidden - views.passwordField.hidePassword() - } - // Success is handled by the LoginActivity - is Success -> Unit + if (state.isLoading) { + // Ensure password is hidden + views.passwordField.hidePassword() } } diff --git a/vector/src/main/java/im/vector/app/features/onboarding/ftueauth/FtueAuthResetPasswordFragment.kt b/vector/src/main/java/im/vector/app/features/onboarding/ftueauth/FtueAuthResetPasswordFragment.kt index 6a224dfae8..4ec02f677a 100644 --- a/vector/src/main/java/im/vector/app/features/onboarding/ftueauth/FtueAuthResetPasswordFragment.kt +++ b/vector/src/main/java/im/vector/app/features/onboarding/ftueauth/FtueAuthResetPasswordFragment.kt @@ -21,9 +21,6 @@ import android.view.LayoutInflater import android.view.View import android.view.ViewGroup import androidx.lifecycle.lifecycleScope -import com.airbnb.mvrx.Fail -import com.airbnb.mvrx.Loading -import com.airbnb.mvrx.Success import com.google.android.material.dialog.MaterialAlertDialogBuilder import im.vector.app.R import im.vector.app.core.extensions.hideKeyboard @@ -54,12 +51,15 @@ class FtueAuthResetPasswordFragment @Inject constructor() : AbstractFtueAuthFrag override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) - setupSubmitButton() } + override fun onError(throwable: Throwable) { + views.resetPasswordEmailTil.error = errorFormatter.toHumanReadable(throwable) + } + private fun setupUi(state: OnboardingViewState) { - views.resetPasswordTitle.text = getString(R.string.login_reset_password_on, state.homeServerUrlFromUser.toReducedUrl()) + views.resetPasswordTitle.text = getString(R.string.login_reset_password_on, state.selectedHomeserver.userFacingUrl.toReducedUrl()) } private fun setupSubmitButton() { @@ -116,16 +116,9 @@ class FtueAuthResetPasswordFragment @Inject constructor() : AbstractFtueAuthFrag override fun updateWithState(state: OnboardingViewState) { setupUi(state) - - when (state.asyncResetPassword) { - is Loading -> { - // Ensure new password is hidden - views.passwordField.hidePassword() - } - is Fail -> { - views.resetPasswordEmailTil.error = errorFormatter.toHumanReadable(state.asyncResetPassword.error) - } - is Success -> Unit + if (state.isLoading) { + // Ensure new password is hidden + views.passwordField.hidePassword() } } } diff --git a/vector/src/main/java/im/vector/app/features/onboarding/ftueauth/FtueAuthResetPasswordMailConfirmationFragment.kt b/vector/src/main/java/im/vector/app/features/onboarding/ftueauth/FtueAuthResetPasswordMailConfirmationFragment.kt index 1d5e1aa00a..f6141a4900 100644 --- a/vector/src/main/java/im/vector/app/features/onboarding/ftueauth/FtueAuthResetPasswordMailConfirmationFragment.kt +++ b/vector/src/main/java/im/vector/app/features/onboarding/ftueauth/FtueAuthResetPasswordMailConfirmationFragment.kt @@ -20,8 +20,6 @@ import android.os.Bundle import android.view.LayoutInflater import android.view.View import android.view.ViewGroup -import com.airbnb.mvrx.Fail -import com.airbnb.mvrx.Success import com.google.android.material.dialog.MaterialAlertDialogBuilder import im.vector.app.R import im.vector.app.databinding.FragmentLoginResetPasswordMailConfirmationBinding @@ -59,23 +57,20 @@ class FtueAuthResetPasswordMailConfirmationFragment @Inject constructor() : Abst override fun updateWithState(state: OnboardingViewState) { setupUi(state) + } - when (state.asyncResetMailConfirmed) { - is Fail -> { - // Link in email not yet clicked ? - val message = if (state.asyncResetMailConfirmed.error.is401()) { - getString(R.string.auth_reset_password_error_unauthorized) - } else { - errorFormatter.toHumanReadable(state.asyncResetMailConfirmed.error) - } - - MaterialAlertDialogBuilder(requireActivity()) - .setTitle(R.string.dialog_title_error) - .setMessage(message) - .setPositiveButton(R.string.ok, null) - .show() - } - is Success -> Unit + override fun onError(throwable: Throwable) { + // Link in email not yet clicked ? + val message = if (throwable.is401()) { + getString(R.string.auth_reset_password_error_unauthorized) + } else { + errorFormatter.toHumanReadable(throwable) } + + MaterialAlertDialogBuilder(requireActivity()) + .setTitle(R.string.dialog_title_error) + .setMessage(message) + .setPositiveButton(R.string.ok, null) + .show() } } diff --git a/vector/src/main/java/im/vector/app/features/onboarding/ftueauth/FtueAuthServerUrlFormFragment.kt b/vector/src/main/java/im/vector/app/features/onboarding/ftueauth/FtueAuthServerUrlFormFragment.kt index 2cae9743a7..df304d028d 100644 --- a/vector/src/main/java/im/vector/app/features/onboarding/ftueauth/FtueAuthServerUrlFormFragment.kt +++ b/vector/src/main/java/im/vector/app/features/onboarding/ftueauth/FtueAuthServerUrlFormFragment.kt @@ -139,7 +139,7 @@ class FtueAuthServerUrlFormFragment @Inject constructor() : AbstractFtueAuthFrag } else -> { views.loginServerUrlFormHomeServerUrl.setText(serverUrl, false /* to avoid completion dialog flicker*/) - viewModel.handle(OnboardingAction.UpdateHomeServer(serverUrl)) + viewModel.handle(OnboardingAction.HomeServerChange.SelectHomeServer(serverUrl)) } } } diff --git a/vector/src/main/java/im/vector/app/features/onboarding/ftueauth/FtueAuthSignUpSignInSelectionFragment.kt b/vector/src/main/java/im/vector/app/features/onboarding/ftueauth/FtueAuthSignUpSignInSelectionFragment.kt index e9ae5022e2..dda5e0c36a 100644 --- a/vector/src/main/java/im/vector/app/features/onboarding/ftueauth/FtueAuthSignUpSignInSelectionFragment.kt +++ b/vector/src/main/java/im/vector/app/features/onboarding/ftueauth/FtueAuthSignUpSignInSelectionFragment.kt @@ -20,6 +20,7 @@ import android.os.Bundle import android.view.LayoutInflater import android.view.View import android.view.ViewGroup +import androidx.annotation.DrawableRes import androidx.core.view.isVisible import com.airbnb.mvrx.withState import im.vector.app.R @@ -55,32 +56,30 @@ class FtueAuthSignUpSignInSelectionFragment @Inject constructor() : AbstractSSOF views.loginSignupSigninSignIn.setOnClickListener { signIn() } } - private fun setupUi(state: OnboardingViewState) { + private fun render(state: OnboardingViewState) { when (state.serverType) { - ServerType.MatrixOrg -> { - views.loginSignupSigninServerIcon.setImageResource(R.drawable.ic_logo_matrix_org) - views.loginSignupSigninServerIcon.isVisible = true - views.loginSignupSigninTitle.text = getString(R.string.login_connect_to, state.homeServerUrlFromUser.toReducedUrl()) - views.loginSignupSigninText.text = getString(R.string.login_server_matrix_org_text) - } - ServerType.EMS -> { - views.loginSignupSigninServerIcon.setImageResource(R.drawable.ic_logo_element_matrix_services) - views.loginSignupSigninServerIcon.isVisible = true - views.loginSignupSigninTitle.text = getString(R.string.login_connect_to_modular) - views.loginSignupSigninText.text = state.homeServerUrlFromUser.toReducedUrl() - } - ServerType.Other -> { - views.loginSignupSigninServerIcon.isVisible = false - views.loginSignupSigninTitle.text = getString(R.string.login_server_other_title) - views.loginSignupSigninText.text = getString(R.string.login_connect_to, state.homeServerUrlFromUser.toReducedUrl()) - } + ServerType.MatrixOrg -> renderServerInformation( + icon = R.drawable.ic_logo_matrix_org, + title = getString(R.string.login_connect_to, state.selectedHomeserver.userFacingUrl.toReducedUrl()), + subtitle = getString(R.string.login_server_matrix_org_text) + ) + ServerType.EMS -> renderServerInformation( + icon = R.drawable.ic_logo_element_matrix_services, + title = getString(R.string.login_connect_to_modular), + subtitle = state.selectedHomeserver.userFacingUrl.toReducedUrl() + ) + ServerType.Other -> renderServerInformation( + icon = null, + title = getString(R.string.login_server_other_title), + subtitle = getString(R.string.login_connect_to, state.selectedHomeserver.userFacingUrl.toReducedUrl()) + ) ServerType.Unknown -> Unit /* Should not happen */ } - when (state.loginMode) { + when (state.selectedHomeserver.preferredLoginMode) { is LoginMode.SsoAndPassword -> { views.loginSignupSigninSignInSocialLoginContainer.isVisible = true - views.loginSignupSigninSocialLoginButtons.ssoIdentityProviders = state.loginMode.ssoIdentityProviders()?.sorted() + views.loginSignupSigninSocialLoginButtons.ssoIdentityProviders = state.selectedHomeserver.preferredLoginMode.ssoIdentityProviders()?.sorted() views.loginSignupSigninSocialLoginButtons.listener = object : SocialLoginButtonsView.InteractionListener { override fun onProviderSelected(id: String?) { viewModel.getSsoUrl( @@ -100,8 +99,16 @@ class FtueAuthSignUpSignInSelectionFragment @Inject constructor() : AbstractSSOF } } + private fun renderServerInformation(@DrawableRes icon: Int?, title: String, subtitle: String) { + icon?.let { views.loginSignupSigninServerIcon.setImageResource(it) } + views.loginSignupSigninServerIcon.isVisible = icon != null + views.loginSignupSigninServerIcon.setImageResource(R.drawable.ic_logo_matrix_org) + views.loginSignupSigninTitle.text = title + views.loginSignupSigninText.text = subtitle + } + private fun setupButtons(state: OnboardingViewState) { - when (state.loginMode) { + when (state.selectedHomeserver.preferredLoginMode) { is LoginMode.Sso -> { // change to only one button that is sign in with sso views.loginSignupSigninSubmit.text = getString(R.string.login_signin_sso) @@ -115,7 +122,7 @@ class FtueAuthSignUpSignInSelectionFragment @Inject constructor() : AbstractSSOF } private fun submit() = withState(viewModel) { state -> - if (state.loginMode is LoginMode.Sso) { + if (state.selectedHomeserver.preferredLoginMode is LoginMode.Sso) { viewModel.getSsoUrl( redirectUrl = SSORedirectRouterActivity.VECTOR_REDIRECT_URL, deviceId = state.deviceId, @@ -136,7 +143,7 @@ class FtueAuthSignUpSignInSelectionFragment @Inject constructor() : AbstractSSOF } override fun updateWithState(state: OnboardingViewState) { - setupUi(state) + render(state) setupButtons(state) } } diff --git a/vector/src/main/java/im/vector/app/features/onboarding/ftueauth/FtueAuthVariant.kt b/vector/src/main/java/im/vector/app/features/onboarding/ftueauth/FtueAuthVariant.kt index 79a974038b..c96a2fbf42 100644 --- a/vector/src/main/java/im/vector/app/features/onboarding/ftueauth/FtueAuthVariant.kt +++ b/vector/src/main/java/im/vector/app/features/onboarding/ftueauth/FtueAuthVariant.kt @@ -17,6 +17,7 @@ package im.vector.app.features.onboarding.ftueauth import android.content.Intent +import android.os.Parcelable import android.view.View import android.view.ViewGroup import androidx.core.view.ViewCompat @@ -31,7 +32,6 @@ import im.vector.app.R import im.vector.app.core.extensions.POP_BACK_STACK_EXCLUSIVE import im.vector.app.core.extensions.addFragment import im.vector.app.core.extensions.addFragmentToBackstack -import im.vector.app.core.extensions.exhaustive import im.vector.app.core.extensions.popBackstack import im.vector.app.core.extensions.replaceFragment import im.vector.app.core.platform.ScreenOrientationLocker @@ -45,17 +45,18 @@ import im.vector.app.features.login.ServerType import im.vector.app.features.login.SignMode import im.vector.app.features.login.TextInputFormFragmentMode import im.vector.app.features.login.isSupported -import im.vector.app.features.login.terms.toLocalizedLoginTerms import im.vector.app.features.onboarding.OnboardingAction import im.vector.app.features.onboarding.OnboardingActivity import im.vector.app.features.onboarding.OnboardingVariant import im.vector.app.features.onboarding.OnboardingViewEvents import im.vector.app.features.onboarding.OnboardingViewModel import im.vector.app.features.onboarding.OnboardingViewState +import im.vector.app.features.onboarding.ftueauth.terms.FtueAuthLegacyStyleTermsFragment import im.vector.app.features.onboarding.ftueauth.terms.FtueAuthTermsFragment -import im.vector.app.features.onboarding.ftueauth.terms.FtueAuthTermsFragmentArgument +import im.vector.app.features.onboarding.ftueauth.terms.FtueAuthTermsLegacyStyleFragmentArgument import org.matrix.android.sdk.api.auth.registration.FlowResult import org.matrix.android.sdk.api.auth.registration.Stage +import org.matrix.android.sdk.api.auth.toLocalizedLoginTerms import org.matrix.android.sdk.api.extensions.tryOrNull private const val FRAGMENT_REGISTRATION_STAGE_TAG = "FRAGMENT_REGISTRATION_STAGE_TAG" @@ -122,7 +123,7 @@ class FtueAuthVariant( private fun updateWithState(viewState: OnboardingViewState) { isForceLoginFallbackEnabled = viewState.isForceLoginFallbackEnabled - views.loginLoading.isVisible = viewState.isLoading() + views.loginLoading.isVisible = viewState.isLoading } override fun setIsLoading(isLoading: Boolean) = Unit @@ -138,10 +139,14 @@ class FtueAuthVariant( // Go on with registration flow handleRegistrationNavigation(viewEvents.flowResult) } else { - // First ask for login and password - // I add a tag to indicate that this fragment is a registration stage. - // This way it will be automatically popped in when starting the next registration stage - openAuthLoginFragmentWithTag(FRAGMENT_REGISTRATION_STAGE_TAG) + if (vectorFeatures.isOnboardingCombinedRegisterEnabled()) { + openCombinedRegister() + } else { + // First ask for login and password + // I add a tag to indicate that this fragment is a registration stage. + // This way it will be automatically popped in when starting the next registration stage + openAuthLoginFragmentWithTag(FRAGMENT_REGISTRATION_STAGE_TAG) + } } } } @@ -198,20 +203,18 @@ class FtueAuthVariant( is OnboardingViewEvents.OnSendEmailSuccess -> { // Pop the enter email Fragment supportFragmentManager.popBackStack(FRAGMENT_REGISTRATION_STAGE_TAG, FragmentManager.POP_BACK_STACK_INCLUSIVE) - activity.addFragmentToBackstack(views.loginFragmentContainer, + addRegistrationStageFragmentToBackstack( FtueAuthWaitForEmailFragment::class.java, FtueAuthWaitForEmailFragmentArgument(viewEvents.email), - tag = FRAGMENT_REGISTRATION_STAGE_TAG, - option = commonOption) + ) } is OnboardingViewEvents.OnSendMsisdnSuccess -> { // Pop the enter Msisdn Fragment supportFragmentManager.popBackStack(FRAGMENT_REGISTRATION_STAGE_TAG, FragmentManager.POP_BACK_STACK_INCLUSIVE) - activity.addFragmentToBackstack(views.loginFragmentContainer, + addRegistrationStageFragmentToBackstack( FtueAuthGenericTextInputFormFragment::class.java, FtueAuthGenericTextInputFormFragmentArgument(TextInputFormFragmentMode.ConfirmMsisdn, true, viewEvents.msisdn), - tag = FRAGMENT_REGISTRATION_STAGE_TAG, - option = commonOption) + ) } is OnboardingViewEvents.Failure, is OnboardingViewEvents.Loading -> @@ -222,6 +225,7 @@ class FtueAuthVariant( FtueAuthUseCaseFragment::class.java, option = commonOption) } + OnboardingViewEvents.OpenCombinedRegister -> openCombinedRegister() is OnboardingViewEvents.OnAccountCreated -> onAccountCreated() OnboardingViewEvents.OnAccountSignedIn -> onAccountSignedIn() OnboardingViewEvents.OnChooseDisplayName -> onChooseDisplayName() @@ -229,7 +233,19 @@ class FtueAuthVariant( OnboardingViewEvents.OnChooseProfilePicture -> onChooseProfilePicture() OnboardingViewEvents.OnPersonalizationComplete -> onPersonalizationComplete() OnboardingViewEvents.OnBack -> activity.popBackstack() - }.exhaustive + OnboardingViewEvents.EditServerSelection -> { + activity.addFragmentToBackstack( + views.loginFragmentContainer, + FtueAuthCombinedServerSelectionFragment::class.java, + option = commonOption + ) + } + OnboardingViewEvents.OnHomeserverEdited -> activity.popBackstack() + } + } + + private fun openCombinedRegister() { + addRegistrationStageFragmentToBackstack(FtueAuthCombinedRegisterFragment::class.java) } private fun registrationShouldFallback(registrationFlowResult: OnboardingViewEvents.RegistrationFlowResult) = @@ -281,23 +297,23 @@ class FtueAuthVariant( SignMode.SignUp -> Unit // This case is processed in handleOnboardingViewEvents SignMode.SignIn -> handleSignInSelected(state) SignMode.SignInWithMatrixId -> handleSignInWithMatrixId(state) - }.exhaustive + } } private fun handleSignInSelected(state: OnboardingViewState) { if (isForceLoginFallbackEnabled) { - onLoginModeNotSupported(state.loginModeSupportedTypes) + onLoginModeNotSupported(state.selectedHomeserver.supportedLoginTypes) } else { disambiguateLoginMode(state) } } - private fun disambiguateLoginMode(state: OnboardingViewState) = when (state.loginMode) { + private fun disambiguateLoginMode(state: OnboardingViewState) = when (state.selectedHomeserver.preferredLoginMode) { LoginMode.Unknown, is LoginMode.Sso -> error("Developer error") is LoginMode.SsoAndPassword, LoginMode.Password -> openAuthLoginFragmentWithTag(FRAGMENT_LOGIN_TAG) - LoginMode.Unsupported -> onLoginModeNotSupported(state.loginModeSupportedTypes) + LoginMode.Unsupported -> onLoginModeNotSupported(state.selectedHomeserver.supportedLoginTypes) } private fun openAuthLoginFragmentWithTag(tag: String) { @@ -318,7 +334,7 @@ class FtueAuthVariant( private fun handleSignInWithMatrixId(state: OnboardingViewState) { if (isForceLoginFallbackEnabled) { - onLoginModeNotSupported(state.loginModeSupportedTypes) + onLoginModeNotSupported(state.selectedHomeserver.supportedLoginTypes) } else { openAuthLoginFragmentWithTag(FRAGMENT_LOGIN_TAG) } @@ -361,30 +377,46 @@ class FtueAuthVariant( supportFragmentManager.popBackStack(FRAGMENT_REGISTRATION_STAGE_TAG, FragmentManager.POP_BACK_STACK_INCLUSIVE) when (stage) { - is Stage.ReCaptcha -> activity.addFragmentToBackstack(views.loginFragmentContainer, - FtueAuthCaptchaFragment::class.java, - FtueAuthCaptchaFragmentArgument(stage.publicKey), - tag = FRAGMENT_REGISTRATION_STAGE_TAG, - option = commonOption) - is Stage.Email -> activity.addFragmentToBackstack(views.loginFragmentContainer, + is Stage.ReCaptcha -> onCaptcha(stage) + is Stage.Email -> addRegistrationStageFragmentToBackstack( FtueAuthGenericTextInputFormFragment::class.java, FtueAuthGenericTextInputFormFragmentArgument(TextInputFormFragmentMode.SetEmail, stage.mandatory), - tag = FRAGMENT_REGISTRATION_STAGE_TAG, - option = commonOption) - is Stage.Msisdn -> activity.addFragmentToBackstack(views.loginFragmentContainer, + ) + is Stage.Msisdn -> addRegistrationStageFragmentToBackstack( FtueAuthGenericTextInputFormFragment::class.java, FtueAuthGenericTextInputFormFragmentArgument(TextInputFormFragmentMode.SetMsisdn, stage.mandatory), - tag = FRAGMENT_REGISTRATION_STAGE_TAG, - option = commonOption) - is Stage.Terms -> activity.addFragmentToBackstack(views.loginFragmentContainer, - FtueAuthTermsFragment::class.java, - FtueAuthTermsFragmentArgument(stage.policies.toLocalizedLoginTerms(activity.getString(R.string.resources_language))), - tag = FRAGMENT_REGISTRATION_STAGE_TAG, - option = commonOption) + ) + is Stage.Terms -> onTerms(stage) else -> Unit // Should not happen } } + private fun onTerms(stage: Stage.Terms) { + when { + vectorFeatures.isOnboardingCombinedRegisterEnabled() -> addRegistrationStageFragmentToBackstack( + FtueAuthTermsFragment::class.java, + FtueAuthTermsLegacyStyleFragmentArgument(stage.policies.toLocalizedLoginTerms(activity.getString(R.string.resources_language))), + ) + else -> addRegistrationStageFragmentToBackstack( + FtueAuthLegacyStyleTermsFragment::class.java, + FtueAuthTermsLegacyStyleFragmentArgument(stage.policies.toLocalizedLoginTerms(activity.getString(R.string.resources_language))), + ) + } + } + + private fun onCaptcha(stage: Stage.ReCaptcha) { + when { + vectorFeatures.isOnboardingCombinedRegisterEnabled() -> addRegistrationStageFragmentToBackstack( + FtueAuthCaptchaFragment::class.java, + FtueAuthCaptchaFragmentArgument(stage.publicKey), + ) + else -> addRegistrationStageFragmentToBackstack( + FtueAuthLegacyStyleCaptchaFragment::class.java, + FtueAuthLegacyStyleCaptchaFragmentArgument(stage.publicKey), + ) + } + } + private fun onAccountSignedIn() { navigateToHome(createdAccount = false) } @@ -426,4 +458,14 @@ class FtueAuthVariant( useCustomAnimation = true ) } + + private fun addRegistrationStageFragmentToBackstack(fragmentClass: Class, params: Parcelable? = null) { + activity.addFragmentToBackstack( + views.loginFragmentContainer, + fragmentClass, + params, + tag = FRAGMENT_REGISTRATION_STAGE_TAG, + option = commonOption + ) + } } diff --git a/vector/src/main/java/im/vector/app/features/onboarding/ftueauth/FtueAuthWaitForEmailFragment.kt b/vector/src/main/java/im/vector/app/features/onboarding/ftueauth/FtueAuthWaitForEmailFragment.kt index ec72f52b9e..6056cd30d3 100644 --- a/vector/src/main/java/im/vector/app/features/onboarding/ftueauth/FtueAuthWaitForEmailFragment.kt +++ b/vector/src/main/java/im/vector/app/features/onboarding/ftueauth/FtueAuthWaitForEmailFragment.kt @@ -78,6 +78,6 @@ class FtueAuthWaitForEmailFragment @Inject constructor() : AbstractFtueAuthFragm } override fun resetViewModel() { - viewModel.handle(OnboardingAction.ResetLogin) + viewModel.handle(OnboardingAction.ResetAuthenticationAttempt) } } diff --git a/vector/src/main/java/im/vector/app/features/onboarding/ftueauth/FtueAuthWebFragment.kt b/vector/src/main/java/im/vector/app/features/onboarding/ftueauth/FtueAuthWebFragment.kt index 4c99a4d1d8..26b9a38d83 100644 --- a/vector/src/main/java/im/vector/app/features/onboarding/ftueauth/FtueAuthWebFragment.kt +++ b/vector/src/main/java/im/vector/app/features/onboarding/ftueauth/FtueAuthWebFragment.kt @@ -40,7 +40,7 @@ import im.vector.app.features.onboarding.OnboardingAction import im.vector.app.features.onboarding.OnboardingViewEvents import im.vector.app.features.onboarding.OnboardingViewState import org.matrix.android.sdk.api.auth.data.Credentials -import org.matrix.android.sdk.internal.di.MoshiProvider +import org.matrix.android.sdk.api.util.MatrixJsonParser import timber.log.Timber import java.net.URLDecoder import javax.inject.Inject @@ -200,7 +200,7 @@ class FtueAuthWebFragment @Inject constructor( try { // URL decode json = URLDecoder.decode(json, "UTF-8") - val adapter = MoshiProvider.providesMoshi().adapter(JavascriptResponse::class.java) + val adapter = MatrixJsonParser.getMoshi().adapter(JavascriptResponse::class.java) javascriptResponse = adapter.fromJson(json) } catch (e: Exception) { Timber.e(e, "## shouldOverrideUrlLoading() : fromJson failed") @@ -235,7 +235,7 @@ class FtueAuthWebFragment @Inject constructor( } override fun resetViewModel() { - viewModel.handle(OnboardingAction.ResetLogin) + viewModel.handle(OnboardingAction.ResetAuthenticationAttempt) } override fun onBackPressed(toolbarButton: Boolean): Boolean { diff --git a/vector/src/main/java/im/vector/app/features/onboarding/ftueauth/terms/FtueAuthLegacyStyleTermsFragment.kt b/vector/src/main/java/im/vector/app/features/onboarding/ftueauth/terms/FtueAuthLegacyStyleTermsFragment.kt new file mode 100755 index 0000000000..727e3d5cbd --- /dev/null +++ b/vector/src/main/java/im/vector/app/features/onboarding/ftueauth/terms/FtueAuthLegacyStyleTermsFragment.kt @@ -0,0 +1,124 @@ +/* + * Copyright 2018 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package im.vector.app.features.onboarding.ftueauth.terms + +import android.os.Bundle +import android.os.Parcelable +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import com.airbnb.mvrx.args +import im.vector.app.core.extensions.cleanup +import im.vector.app.core.extensions.configureWith +import im.vector.app.core.extensions.toReducedUrl +import im.vector.app.core.utils.openUrlInChromeCustomTab +import im.vector.app.databinding.FragmentLoginTermsBinding +import im.vector.app.features.login.terms.LocalizedFlowDataLoginTermsChecked +import im.vector.app.features.login.terms.LoginTermsViewState +import im.vector.app.features.login.terms.PolicyController +import im.vector.app.features.onboarding.OnboardingAction +import im.vector.app.features.onboarding.OnboardingViewState +import im.vector.app.features.onboarding.RegisterAction +import im.vector.app.features.onboarding.ftueauth.AbstractFtueAuthFragment +import kotlinx.parcelize.Parcelize +import org.matrix.android.sdk.api.auth.data.LocalizedFlowDataLoginTerms +import javax.inject.Inject + +@Parcelize +data class FtueAuthTermsLegacyStyleFragmentArgument( + val localizedFlowDataLoginTerms: List +) : Parcelable + +/** + * LoginTermsFragment displays the list of policies the user has to accept + */ +class FtueAuthLegacyStyleTermsFragment @Inject constructor( + private val policyController: PolicyController +) : AbstractFtueAuthFragment(), + PolicyController.PolicyControllerListener { + + private val params: FtueAuthTermsLegacyStyleFragmentArgument by args() + + override fun getBinding(inflater: LayoutInflater, container: ViewGroup?): FragmentLoginTermsBinding { + return FragmentLoginTermsBinding.inflate(inflater, container, false) + } + + private var loginTermsViewState: LoginTermsViewState = LoginTermsViewState(emptyList()) + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + + setupViews() + views.loginTermsPolicyList.configureWith(policyController) + policyController.listener = this + + val list = ArrayList() + + params.localizedFlowDataLoginTerms + .forEach { + list.add(LocalizedFlowDataLoginTermsChecked(it)) + } + + loginTermsViewState = LoginTermsViewState(list) + } + + private fun setupViews() { + views.loginTermsSubmit.setOnClickListener { submit() } + } + + override fun onDestroyView() { + views.loginTermsPolicyList.cleanup() + policyController.listener = null + super.onDestroyView() + } + + private fun renderState() { + policyController.setData(loginTermsViewState.localizedFlowDataLoginTermsChecked) + views.loginTermsSubmit.isEnabled = loginTermsViewState.allChecked() + } + + override fun setChecked(localizedFlowDataLoginTerms: LocalizedFlowDataLoginTerms, isChecked: Boolean) { + if (isChecked) { + loginTermsViewState.check(localizedFlowDataLoginTerms) + } else { + loginTermsViewState.uncheck(localizedFlowDataLoginTerms) + } + + renderState() + } + + override fun openPolicy(localizedFlowDataLoginTerms: LocalizedFlowDataLoginTerms) { + localizedFlowDataLoginTerms.localizedUrl + ?.takeIf { it.isNotBlank() } + ?.let { + openUrlInChromeCustomTab(requireContext(), null, it) + } + } + + private fun submit() { + viewModel.handle(OnboardingAction.PostRegisterAction(RegisterAction.AcceptTerms)) + } + + override fun updateWithState(state: OnboardingViewState) { + policyController.homeServer = state.selectedHomeserver.userFacingUrl.toReducedUrl() + renderState() + } + + override fun resetViewModel() { + viewModel.handle(OnboardingAction.ResetAuthenticationAttempt) + } +} diff --git a/vector/src/main/java/im/vector/app/features/onboarding/ftueauth/terms/FtueAuthTermsFragment.kt b/vector/src/main/java/im/vector/app/features/onboarding/ftueauth/terms/FtueAuthTermsFragment.kt old mode 100755 new mode 100644 index 03598d3a47..4a25e35537 --- a/vector/src/main/java/im/vector/app/features/onboarding/ftueauth/terms/FtueAuthTermsFragment.kt +++ b/vector/src/main/java/im/vector/app/features/onboarding/ftueauth/terms/FtueAuthTermsFragment.kt @@ -1,5 +1,5 @@ /* - * Copyright 2018 New Vector Ltd + * Copyright (c) 2022 New Vector Ltd * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -17,16 +17,18 @@ package im.vector.app.features.onboarding.ftueauth.terms import android.os.Bundle -import android.os.Parcelable import android.view.LayoutInflater import android.view.View import android.view.ViewGroup +import androidx.constraintlayout.widget.ConstraintLayout +import androidx.core.view.doOnLayout import com.airbnb.mvrx.args +import im.vector.app.R import im.vector.app.core.extensions.cleanup import im.vector.app.core.extensions.configureWith import im.vector.app.core.extensions.toReducedUrl import im.vector.app.core.utils.openUrlInChromeCustomTab -import im.vector.app.databinding.FragmentLoginTermsBinding +import im.vector.app.databinding.FragmentFtueLoginTermsBinding import im.vector.app.features.login.terms.LocalizedFlowDataLoginTermsChecked import im.vector.app.features.login.terms.LoginTermsViewState import im.vector.app.features.login.terms.PolicyController @@ -34,50 +36,46 @@ import im.vector.app.features.onboarding.OnboardingAction import im.vector.app.features.onboarding.OnboardingViewState import im.vector.app.features.onboarding.RegisterAction import im.vector.app.features.onboarding.ftueauth.AbstractFtueAuthFragment -import kotlinx.parcelize.Parcelize -import org.matrix.android.sdk.internal.auth.registration.LocalizedFlowDataLoginTerms +import org.matrix.android.sdk.api.auth.data.LocalizedFlowDataLoginTerms import javax.inject.Inject - -@Parcelize -data class FtueAuthTermsFragmentArgument( - val localizedFlowDataLoginTerms: List -) : Parcelable +import kotlin.math.roundToInt /** * LoginTermsFragment displays the list of policies the user has to accept */ class FtueAuthTermsFragment @Inject constructor( private val policyController: PolicyController -) : AbstractFtueAuthFragment(), +) : AbstractFtueAuthFragment(), PolicyController.PolicyControllerListener { - private val params: FtueAuthTermsFragmentArgument by args() + private val params: FtueAuthTermsLegacyStyleFragmentArgument by args() - override fun getBinding(inflater: LayoutInflater, container: ViewGroup?): FragmentLoginTermsBinding { - return FragmentLoginTermsBinding.inflate(inflater, container, false) + override fun getBinding(inflater: LayoutInflater, container: ViewGroup?): FragmentFtueLoginTermsBinding { + return FragmentFtueLoginTermsBinding.inflate(inflater, container, false) } private var loginTermsViewState: LoginTermsViewState = LoginTermsViewState(emptyList()) override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) - setupViews() - views.loginTermsPolicyList.configureWith(policyController) - policyController.listener = this - val list = ArrayList() - params.localizedFlowDataLoginTerms .forEach { list.add(LocalizedFlowDataLoginTermsChecked(it)) } - loginTermsViewState = LoginTermsViewState(list) } private fun setupViews() { - views.loginTermsSubmit.setOnClickListener { submit() } + views.termsSubmit.setOnClickListener { submit() } + views.loginTermsPolicyList.setHasFixedSize(false) + views.loginTermsPolicyList.configureWith(policyController, hasFixedSize = false, dividerDrawable = R.drawable.divider_horizontal) + views.termsGutterStart.doOnLayout { + val gutterSize = views.contentRoot.width * (views.termsGutterStart.layoutParams as ConstraintLayout.LayoutParams).guidePercent + policyController.horizontalPadding = gutterSize.roundToInt() + } + policyController.listener = this } override fun onDestroyView() { @@ -90,7 +88,7 @@ class FtueAuthTermsFragment @Inject constructor( policyController.setData(loginTermsViewState.localizedFlowDataLoginTermsChecked) // Button is enabled only if all checkboxes are checked - views.loginTermsSubmit.isEnabled = loginTermsViewState.allChecked() + views.termsSubmit.isEnabled = loginTermsViewState.allChecked() } override fun setChecked(localizedFlowDataLoginTerms: LocalizedFlowDataLoginTerms, isChecked: Boolean) { @@ -116,11 +114,11 @@ class FtueAuthTermsFragment @Inject constructor( } override fun updateWithState(state: OnboardingViewState) { - policyController.homeServer = state.homeServerUrlFromUser.toReducedUrl() + policyController.homeServer = state.selectedHomeserver.userFacingUrl.toReducedUrl() renderState() } override fun resetViewModel() { - viewModel.handle(OnboardingAction.ResetLogin) + viewModel.handle(OnboardingAction.ResetAuthenticationAttempt) } } diff --git a/vector/src/main/java/im/vector/app/features/permalink/PermalinkHandler.kt b/vector/src/main/java/im/vector/app/features/permalink/PermalinkHandler.kt index fa7b5aa7bc..cc2d712bb2 100644 --- a/vector/src/main/java/im/vector/app/features/permalink/PermalinkHandler.kt +++ b/vector/src/main/java/im/vector/app/features/permalink/PermalinkHandler.kt @@ -32,12 +32,15 @@ import kotlinx.coroutines.withContext import org.matrix.android.sdk.api.extensions.orFalse import org.matrix.android.sdk.api.extensions.tryOrNull import org.matrix.android.sdk.api.session.events.model.getRootThreadEventId +import org.matrix.android.sdk.api.session.getRoom +import org.matrix.android.sdk.api.session.getRoomSummary import org.matrix.android.sdk.api.session.permalinks.PermalinkData import org.matrix.android.sdk.api.session.permalinks.PermalinkParser import org.matrix.android.sdk.api.session.permalinks.PermalinkService import org.matrix.android.sdk.api.session.room.model.Membership import org.matrix.android.sdk.api.session.room.model.RoomSummary import org.matrix.android.sdk.api.session.room.model.RoomType +import org.matrix.android.sdk.api.session.room.timeline.isRootThread import javax.inject.Inject class PermalinkHandler @Inject constructor(private val activeSessionHolder: ActiveSessionHolder, @@ -89,7 +92,13 @@ class PermalinkHandler @Inject constructor(private val activeSessionHolder: Acti val rootThreadEventId = permalinkData.eventId?.let { eventId -> val room = roomId?.let { session?.getRoom(it) } - room?.getTimelineEvent(eventId)?.root?.getRootThreadEventId() + + val rootThreadEventId = room?.getTimelineEvent(eventId)?.root?.getRootThreadEventId() + rootThreadEventId ?: if (room?.getTimelineEvent(eventId)?.isRootThread() == true) { + eventId + } else { + null + } } openRoom( navigationInterceptor, @@ -138,7 +147,7 @@ class PermalinkHandler @Inject constructor(private val activeSessionHolder: Acti private suspend fun PermalinkData.RoomLink.getRoomId(): String? { val session = activeSessionHolder.getSafeActiveSession() return if (isRoomAlias && session != null) { - val roomIdByAlias = session.getRoomIdByAlias(roomIdOrAlias, true) + val roomIdByAlias = session.roomService().getRoomIdByAlias(roomIdOrAlias, true) roomIdByAlias.getOrNull()?.roomId } else { roomIdOrAlias diff --git a/vector/src/main/java/im/vector/app/features/poll/create/PollMode.kt b/vector/src/main/java/im/vector/app/features/poll/PollMode.kt similarity index 93% rename from vector/src/main/java/im/vector/app/features/poll/create/PollMode.kt rename to vector/src/main/java/im/vector/app/features/poll/PollMode.kt index 0007589d10..47558a34a4 100644 --- a/vector/src/main/java/im/vector/app/features/poll/create/PollMode.kt +++ b/vector/src/main/java/im/vector/app/features/poll/PollMode.kt @@ -14,7 +14,7 @@ * limitations under the License. */ -package im.vector.app.features.poll.create +package im.vector.app.features.poll enum class PollMode { CREATE, diff --git a/vector/src/main/java/im/vector/app/features/poll/PollState.kt b/vector/src/main/java/im/vector/app/features/poll/PollState.kt new file mode 100644 index 0000000000..93cdb0ecbe --- /dev/null +++ b/vector/src/main/java/im/vector/app/features/poll/PollState.kt @@ -0,0 +1,27 @@ +/* + * Copyright (c) 2022 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package im.vector.app.features.poll + +sealed interface PollState { + object Sending : PollState + object Ready : PollState + data class Voted(val votes: Int) : PollState + object Undisclosed : PollState + object Ended : PollState + + fun isVotable() = this !is Sending && this !is Ended +} diff --git a/vector/src/main/java/im/vector/app/features/poll/create/CreatePollFragment.kt b/vector/src/main/java/im/vector/app/features/poll/create/CreatePollFragment.kt index 4483b00158..590c181ef5 100644 --- a/vector/src/main/java/im/vector/app/features/poll/create/CreatePollFragment.kt +++ b/vector/src/main/java/im/vector/app/features/poll/create/CreatePollFragment.kt @@ -27,9 +27,9 @@ import com.airbnb.mvrx.args import com.airbnb.mvrx.withState import im.vector.app.R import im.vector.app.core.extensions.configureWith -import im.vector.app.core.extensions.exhaustive import im.vector.app.core.platform.VectorBaseFragment import im.vector.app.databinding.FragmentCreatePollBinding +import im.vector.app.features.poll.PollMode import im.vector.app.features.poll.create.CreatePollViewModel.Companion.MAX_OPTIONS_COUNT import kotlinx.parcelize.Parcelize import org.matrix.android.sdk.api.session.room.model.message.PollType @@ -68,7 +68,7 @@ class CreatePollFragment @Inject constructor( views.createPollToolbar.title = getString(R.string.edit_poll_title) views.createPollButton.text = getString(R.string.edit_poll_title) } - }.exhaustive + } views.createPollRecyclerView.configureWith(controller, disableItemAnimation = true) // workaround for https://github.com/vector-im/element-android/issues/4735 diff --git a/vector/src/main/java/im/vector/app/features/poll/create/CreatePollViewModel.kt b/vector/src/main/java/im/vector/app/features/poll/create/CreatePollViewModel.kt index 2358f7f9a0..1840aa261b 100644 --- a/vector/src/main/java/im/vector/app/features/poll/create/CreatePollViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/poll/create/CreatePollViewModel.kt @@ -23,7 +23,9 @@ import dagger.assisted.AssistedInject import im.vector.app.core.di.MavericksAssistedViewModelFactory import im.vector.app.core.di.hiltMavericksViewModelFactory import im.vector.app.core.platform.VectorViewModel +import im.vector.app.features.poll.PollMode import org.matrix.android.sdk.api.session.Session +import org.matrix.android.sdk.api.session.getRoom import org.matrix.android.sdk.api.session.room.model.message.MessagePollContent import org.matrix.android.sdk.api.session.room.model.message.PollType import org.matrix.android.sdk.api.session.room.timeline.getLastMessageContent diff --git a/vector/src/main/java/im/vector/app/features/poll/create/CreatePollViewState.kt b/vector/src/main/java/im/vector/app/features/poll/create/CreatePollViewState.kt index fc3b746f32..cfa1ff847e 100644 --- a/vector/src/main/java/im/vector/app/features/poll/create/CreatePollViewState.kt +++ b/vector/src/main/java/im/vector/app/features/poll/create/CreatePollViewState.kt @@ -17,6 +17,7 @@ package im.vector.app.features.poll.create import com.airbnb.mvrx.MavericksState +import im.vector.app.features.poll.PollMode import org.matrix.android.sdk.api.session.room.model.message.PollType data class CreatePollViewState( diff --git a/vector/src/main/java/im/vector/app/features/qrcode/QrCodeScannerActivity.kt b/vector/src/main/java/im/vector/app/features/qrcode/QrCodeScannerActivity.kt index dda7b2e2eb..b23f2f171d 100644 --- a/vector/src/main/java/im/vector/app/features/qrcode/QrCodeScannerActivity.kt +++ b/vector/src/main/java/im/vector/app/features/qrcode/QrCodeScannerActivity.kt @@ -24,7 +24,6 @@ import androidx.activity.result.ActivityResultLauncher import com.airbnb.mvrx.viewModel import dagger.hilt.android.AndroidEntryPoint import im.vector.app.R -import im.vector.app.core.extensions.exhaustive import im.vector.app.core.extensions.replaceFragment import im.vector.app.core.platform.VectorBaseActivity import im.vector.app.databinding.ActivitySimpleBinding @@ -51,7 +50,7 @@ class QrCodeScannerActivity() : VectorBaseActivity() { finish() } else -> Unit - }.exhaustive + } } if (isFirstCreation()) { diff --git a/vector/src/main/java/im/vector/app/features/rageshake/BugReportActivity.kt b/vector/src/main/java/im/vector/app/features/rageshake/BugReportActivity.kt index 2d4bc704a4..f0edf6fd57 100755 --- a/vector/src/main/java/im/vector/app/features/rageshake/BugReportActivity.kt +++ b/vector/src/main/java/im/vector/app/features/rageshake/BugReportActivity.kt @@ -63,11 +63,11 @@ class BugReportActivity : VectorBaseActivity() { // Default screen is for bug report, so modify it for suggestion when (reportType) { - ReportType.BUG_REPORT -> { + ReportType.BUG_REPORT -> { supportActionBar?.setTitle(R.string.title_activity_bug_report) views.bugReportButtonContactMe.isVisible = true } - ReportType.SUGGESTION -> { + ReportType.SUGGESTION -> { supportActionBar?.setTitle(R.string.send_suggestion) views.bugReportFirstText.setText(R.string.send_suggestion_content) @@ -76,7 +76,7 @@ class BugReportActivity : VectorBaseActivity() { hideBugReportOptions() } - ReportType.SPACE_BETA_FEEDBACK -> { + ReportType.SPACE_BETA_FEEDBACK -> { supportActionBar?.setTitle(R.string.send_feedback_space_title) views.bugReportFirstText.setText(R.string.send_feedback_space_info) @@ -85,7 +85,16 @@ class BugReportActivity : VectorBaseActivity() { hideBugReportOptions() } - else -> { + ReportType.THREADS_BETA_FEEDBACK -> { + supportActionBar?.setTitle(R.string.send_feedback_threads_title) + + views.bugReportFirstText.setText(R.string.send_feedback_threads_info) + views.bugReportTextInputLayout.hint = getString(R.string.feedback) + views.bugReportButtonContactMe.isVisible = true + + hideBugReportOptions() + } + else -> { // other types not supported here } } diff --git a/vector/src/main/java/im/vector/app/features/rageshake/BugReportViewModel.kt b/vector/src/main/java/im/vector/app/features/rageshake/BugReportViewModel.kt index d0a1280868..6437f5c8f8 100644 --- a/vector/src/main/java/im/vector/app/features/rageshake/BugReportViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/rageshake/BugReportViewModel.kt @@ -39,7 +39,7 @@ class BugReportViewModel @AssistedInject constructor( override fun create(initialState: BugReportState): BugReportViewModel } - companion object : MavericksViewModelFactory by hiltMavericksViewModelFactory() + companion object : MavericksViewModelFactory by hiltMavericksViewModelFactory() init { fetchHomeserverVersion() diff --git a/vector/src/main/java/im/vector/app/features/rageshake/BugReporter.kt b/vector/src/main/java/im/vector/app/features/rageshake/BugReporter.kt index 6434ba60f2..8b514f4003 100755 --- a/vector/src/main/java/im/vector/app/features/rageshake/BugReporter.kt +++ b/vector/src/main/java/im/vector/app/features/rageshake/BugReporter.kt @@ -51,8 +51,8 @@ import org.json.JSONException import org.json.JSONObject import org.matrix.android.sdk.api.Matrix import org.matrix.android.sdk.api.util.JsonDict +import org.matrix.android.sdk.api.util.MatrixJsonParser import org.matrix.android.sdk.api.util.MimeTypes -import org.matrix.android.sdk.internal.di.MoshiProvider import timber.log.Timber import java.io.File import java.io.IOException @@ -98,7 +98,7 @@ class BugReporter @Inject constructor( // boolean to cancel the bug report private val mIsCancelled = false - val adapter = MoshiProvider.providesMoshi() + val adapter = MatrixJsonParser.getMoshi() .adapter(Types.newParameterizedType(Map::class.java, String::class.java, Any::class.java)) /** @@ -255,11 +255,12 @@ class BugReporter @Inject constructor( if (!mIsCancelled) { val text = when (reportType) { - ReportType.BUG_REPORT -> "[Element] $bugDescription" - ReportType.SUGGESTION -> "[Element] [Suggestion] $bugDescription" - ReportType.SPACE_BETA_FEEDBACK -> "[Element] [spaces-feedback] $bugDescription" + ReportType.BUG_REPORT -> "[Element] $bugDescription" + ReportType.SUGGESTION -> "[Element] [Suggestion] $bugDescription" + ReportType.SPACE_BETA_FEEDBACK -> "[Element] [spaces-feedback] $bugDescription" + ReportType.THREADS_BETA_FEEDBACK -> "[Element] [threads-feedback] $bugDescription" ReportType.AUTO_UISI_SENDER, - ReportType.AUTO_UISI -> bugDescription + ReportType.AUTO_UISI -> bugDescription } // build the multi part request @@ -335,17 +336,18 @@ class BugReporter @Inject constructor( builder.addFormDataPart("label", "[Element]") when (reportType) { - ReportType.BUG_REPORT -> { + ReportType.BUG_REPORT -> { /* nop */ } - ReportType.SUGGESTION -> builder.addFormDataPart("label", "[Suggestion]") - ReportType.SPACE_BETA_FEEDBACK -> builder.addFormDataPart("label", "spaces-feedback") - ReportType.AUTO_UISI -> { + ReportType.SUGGESTION -> builder.addFormDataPart("label", "[Suggestion]") + ReportType.SPACE_BETA_FEEDBACK -> builder.addFormDataPart("label", "spaces-feedback") + ReportType.THREADS_BETA_FEEDBACK -> builder.addFormDataPart("label", "threads-feedback") + ReportType.AUTO_UISI -> { builder.addFormDataPart("label", "Z-UISI") builder.addFormDataPart("label", "android") builder.addFormDataPart("label", "uisi-recipient") } - ReportType.AUTO_UISI_SENDER -> { + ReportType.AUTO_UISI_SENDER -> { builder.addFormDataPart("label", "Z-UISI") builder.addFormDataPart("label", "android") builder.addFormDataPart("label", "uisi-sender") diff --git a/vector/src/main/java/im/vector/app/features/rageshake/RageShake.kt b/vector/src/main/java/im/vector/app/features/rageshake/RageShake.kt index b4dcb07349..b5dd3f1ef0 100644 --- a/vector/src/main/java/im/vector/app/features/rageshake/RageShake.kt +++ b/vector/src/main/java/im/vector/app/features/rageshake/RageShake.kt @@ -24,6 +24,7 @@ import androidx.fragment.app.FragmentActivity import com.google.android.material.dialog.MaterialAlertDialogBuilder import com.squareup.seismic.ShakeDetector import im.vector.app.R +import im.vector.app.core.di.ActiveSessionHolder import im.vector.app.core.hardware.vibrate import im.vector.app.features.navigation.Navigator import im.vector.app.features.settings.VectorPreferences @@ -33,6 +34,7 @@ import javax.inject.Inject class RageShake @Inject constructor(private val activity: FragmentActivity, private val bugReporter: BugReporter, private val navigator: Navigator, + private val sessionHolder: ActiveSessionHolder, private val vectorPreferences: VectorPreferences) : ShakeDetector.Listener { private var shakeDetector: ShakeDetector? = null @@ -75,7 +77,13 @@ class RageShake @Inject constructor(private val activity: FragmentActivity, MaterialAlertDialogBuilder(activity) .setMessage(R.string.send_bug_report_alert_message) .setPositiveButton(R.string.yes) { _, _ -> openBugReportScreen() } - .setNeutralButton(R.string.settings) { _, _ -> openSettings() } + .also { + if (sessionHolder.hasActiveSession()) { + it.setNeutralButton(R.string.settings) { _, _ -> openSettings() } + } else { + it.setNeutralButton(R.string.action_disable) { _, _ -> disableRageShake() } + } + } .setOnDismissListener { dialogDisplayed = false } .setNegativeButton(R.string.no, null) .show() @@ -90,6 +98,11 @@ class RageShake @Inject constructor(private val activity: FragmentActivity, navigator.openSettings(activity, VectorSettingsActivity.EXTRA_DIRECT_ACCESS_ADVANCED_SETTINGS) } + private fun disableRageShake() { + vectorPreferences.setRageshakeEnabled(false) + stop() + } + companion object { /** * Check if the feature is available diff --git a/vector/src/main/java/im/vector/app/features/rageshake/ReportType.kt b/vector/src/main/java/im/vector/app/features/rageshake/ReportType.kt index f9dc628914..f75420ea55 100644 --- a/vector/src/main/java/im/vector/app/features/rageshake/ReportType.kt +++ b/vector/src/main/java/im/vector/app/features/rageshake/ReportType.kt @@ -1,5 +1,5 @@ /* - * Copyright (c) 2021 New Vector Ltd + * Copyright (c) 2022 New Vector Ltd * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -20,6 +20,7 @@ enum class ReportType { BUG_REPORT, SUGGESTION, SPACE_BETA_FEEDBACK, + THREADS_BETA_FEEDBACK, AUTO_UISI, AUTO_UISI_SENDER, } diff --git a/vector/src/main/java/im/vector/app/features/raw/wellknown/ElementWellKnownMapper.kt b/vector/src/main/java/im/vector/app/features/raw/wellknown/ElementWellKnownMapper.kt index 899a7d3fe6..06661cb929 100644 --- a/vector/src/main/java/im/vector/app/features/raw/wellknown/ElementWellKnownMapper.kt +++ b/vector/src/main/java/im/vector/app/features/raw/wellknown/ElementWellKnownMapper.kt @@ -18,11 +18,11 @@ package im.vector.app.features.raw.wellknown import com.squareup.moshi.JsonAdapter import org.matrix.android.sdk.api.extensions.tryOrNull -import org.matrix.android.sdk.internal.di.MoshiProvider +import org.matrix.android.sdk.api.util.MatrixJsonParser object ElementWellKnownMapper { - val adapter: JsonAdapter = MoshiProvider.providesMoshi().adapter(ElementWellKnown::class.java) + val adapter: JsonAdapter = MatrixJsonParser.getMoshi().adapter(ElementWellKnown::class.java) fun from(value: String): ElementWellKnown? { return tryOrNull("Unable to parse well-known data") { adapter.fromJson(value) } diff --git a/vector/src/main/java/im/vector/app/features/reactions/EmojiSearchResultViewModel.kt b/vector/src/main/java/im/vector/app/features/reactions/EmojiSearchResultViewModel.kt index e1e52fdca1..720061e326 100644 --- a/vector/src/main/java/im/vector/app/features/reactions/EmojiSearchResultViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/reactions/EmojiSearchResultViewModel.kt @@ -36,7 +36,7 @@ data class EmojiSearchResultViewState( class EmojiSearchResultViewModel @AssistedInject constructor( @Assisted initialState: EmojiSearchResultViewState, private val dataSource: EmojiDataSource) : - VectorViewModel(initialState) { + VectorViewModel(initialState) { @AssistedFactory interface Factory : MavericksAssistedViewModelFactory { diff --git a/vector/src/main/java/im/vector/app/features/room/RequireActiveMembershipViewModel.kt b/vector/src/main/java/im/vector/app/features/room/RequireActiveMembershipViewModel.kt index a77bd32f26..9d6172bcea 100644 --- a/vector/src/main/java/im/vector/app/features/room/RequireActiveMembershipViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/room/RequireActiveMembershipViewModel.kt @@ -23,7 +23,6 @@ import dagger.assisted.AssistedInject import im.vector.app.R import im.vector.app.core.di.MavericksAssistedViewModelFactory import im.vector.app.core.di.hiltMavericksViewModelFactory -import im.vector.app.core.extensions.exhaustive import im.vector.app.core.platform.VectorViewModel import im.vector.app.core.resources.StringProvider import kotlinx.coroutines.Dispatchers @@ -37,6 +36,7 @@ import kotlinx.coroutines.flow.onEach import org.matrix.android.sdk.api.query.QueryStringValue import org.matrix.android.sdk.api.session.Session import org.matrix.android.sdk.api.session.events.model.EventType +import org.matrix.android.sdk.api.session.getRoom import org.matrix.android.sdk.api.session.room.Room import org.matrix.android.sdk.api.session.room.model.Membership import org.matrix.android.sdk.api.session.room.model.RoomSummary @@ -51,7 +51,7 @@ class RequireActiveMembershipViewModel @AssistedInject constructor( @Assisted initialState: RequireActiveMembershipViewState, private val stringProvider: StringProvider, private val session: Session) : - VectorViewModel(initialState) { + VectorViewModel(initialState) { @AssistedFactory interface Factory : MavericksAssistedViewModelFactory { @@ -127,6 +127,6 @@ class RequireActiveMembershipViewModel @AssistedInject constructor( } roomIdFlow.tryEmit(Optional.from(action.roomId)) } - }.exhaustive + } } } diff --git a/vector/src/main/java/im/vector/app/features/roomdirectory/ExplicitTermFilter.kt b/vector/src/main/java/im/vector/app/features/roomdirectory/ExplicitTermFilter.kt index e184e77557..b1876d2c4e 100644 --- a/vector/src/main/java/im/vector/app/features/roomdirectory/ExplicitTermFilter.kt +++ b/vector/src/main/java/im/vector/app/features/roomdirectory/ExplicitTermFilter.kt @@ -35,7 +35,7 @@ class ExplicitTermFilter @Inject constructor( .toRegex(RegexOption.IGNORE_CASE) fun canSearchFor(term: String): Boolean { - return term !in explicitTerms && term != "18+" + return term !in explicitTerms && term != "18+" } fun isValid(str: String): Boolean { diff --git a/vector/src/main/java/im/vector/app/features/roomdirectory/PublicRoomsFragment.kt b/vector/src/main/java/im/vector/app/features/roomdirectory/PublicRoomsFragment.kt index 14b50c2745..b8bba347fd 100644 --- a/vector/src/main/java/im/vector/app/features/roomdirectory/PublicRoomsFragment.kt +++ b/vector/src/main/java/im/vector/app/features/roomdirectory/PublicRoomsFragment.kt @@ -28,7 +28,6 @@ import com.airbnb.mvrx.withState import im.vector.app.R import im.vector.app.core.extensions.cleanup import im.vector.app.core.extensions.configureWith -import im.vector.app.core.extensions.exhaustive import im.vector.app.core.extensions.trackItemsVisibilityChange import im.vector.app.core.platform.VectorBaseFragment import im.vector.app.core.platform.showOptimizedSnackbar @@ -96,7 +95,7 @@ class PublicRoomsFragment @Inject constructor( is RoomDirectoryViewEvents.Failure -> { views.coordinatorLayout.showOptimizedSnackbar(errorFormatter.toHumanReadable(viewEvents.throwable)) } - }.exhaustive + } } override fun onDestroyView() { diff --git a/vector/src/main/java/im/vector/app/features/roomdirectory/RoomDirectoryActivity.kt b/vector/src/main/java/im/vector/app/features/roomdirectory/RoomDirectoryActivity.kt index 48da9f4fa0..f0df31342e 100644 --- a/vector/src/main/java/im/vector/app/features/roomdirectory/RoomDirectoryActivity.kt +++ b/vector/src/main/java/im/vector/app/features/roomdirectory/RoomDirectoryActivity.kt @@ -62,8 +62,8 @@ class RoomDirectoryActivity : VectorBaseActivity(), Matri .stream() .onEach { sharedAction -> when (sharedAction) { - is RoomDirectorySharedAction.Back -> popBackstack() - is RoomDirectorySharedAction.CreateRoom -> { + is RoomDirectorySharedAction.Back -> popBackstack() + is RoomDirectorySharedAction.CreateRoom -> { // Transmit the filter to the CreateRoomFragment withState(roomDirectoryViewModel) { addFragmentToBackstack( @@ -73,9 +73,10 @@ class RoomDirectoryActivity : VectorBaseActivity(), Matri ) } } - is RoomDirectorySharedAction.ChangeProtocol -> + is RoomDirectorySharedAction.ChangeProtocol -> addFragmentToBackstack(views.simpleFragmentContainer, RoomDirectoryPickerFragment::class.java) - is RoomDirectorySharedAction.Close -> finish() + is RoomDirectorySharedAction.Close -> finish() + is RoomDirectorySharedAction.CreateRoomSuccess -> Unit } } .launchIn(lifecycleScope) diff --git a/vector/src/main/java/im/vector/app/features/roomdirectory/RoomDirectoryViewModel.kt b/vector/src/main/java/im/vector/app/features/roomdirectory/RoomDirectoryViewModel.kt index 710d4d5b5f..2cebc96dac 100644 --- a/vector/src/main/java/im/vector/app/features/roomdirectory/RoomDirectoryViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/roomdirectory/RoomDirectoryViewModel.kt @@ -165,7 +165,7 @@ class RoomDirectoryViewModel @AssistedInject constructor( currentJob = viewModelScope.launch { val data = try { - session.getPublicRooms(roomDirectoryData.homeServer, + session.roomDirectoryService().getPublicRooms(roomDirectoryData.homeServer, PublicRoomsParams( limit = PUBLIC_ROOMS_LIMIT, filter = PublicRoomsFilter(searchTerm = filter), @@ -225,7 +225,7 @@ class RoomDirectoryViewModel @AssistedInject constructor( val viaServers = listOfNotNull(state.roomDirectoryData.homeServer) viewModelScope.launch { try { - session.joinRoom(action.publicRoom.roomId, viaServers = viaServers) + session.roomService().joinRoom(action.publicRoom.roomId, viaServers = viaServers) analyticsTracker.capture(action.publicRoom.toAnalyticsJoinedRoom()) // We do not update the joiningRoomsIds here, because, the room is not joined yet regarding the sync data. // Instead, we wait for the room to be joined diff --git a/vector/src/main/java/im/vector/app/features/roomdirectory/createroom/CreateRoomFragment.kt b/vector/src/main/java/im/vector/app/features/roomdirectory/createroom/CreateRoomFragment.kt index 2bd41ae3af..2871513c1f 100644 --- a/vector/src/main/java/im/vector/app/features/roomdirectory/createroom/CreateRoomFragment.kt +++ b/vector/src/main/java/im/vector/app/features/roomdirectory/createroom/CreateRoomFragment.kt @@ -34,7 +34,6 @@ import im.vector.app.R import im.vector.app.core.dialogs.GalleryOrCameraDialogHelper import im.vector.app.core.extensions.cleanup import im.vector.app.core.extensions.configureWith -import im.vector.app.core.extensions.exhaustive import im.vector.app.core.platform.OnBackPressed import im.vector.app.core.platform.VectorBaseFragment import im.vector.app.core.resources.ColorProvider @@ -94,7 +93,7 @@ class CreateRoomFragment @Inject constructor( when (it) { CreateRoomViewEvents.Quit -> vectorBaseActivity.onBackPressed() is CreateRoomViewEvents.Failure -> showFailure(it.throwable) - }.exhaustive + } } } diff --git a/vector/src/main/java/im/vector/app/features/roomdirectory/createroom/CreateRoomViewModel.kt b/vector/src/main/java/im/vector/app/features/roomdirectory/createroom/CreateRoomViewModel.kt index 3b2e9de2d1..caf17e09d6 100644 --- a/vector/src/main/java/im/vector/app/features/roomdirectory/createroom/CreateRoomViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/roomdirectory/createroom/CreateRoomViewModel.kt @@ -28,7 +28,6 @@ import dagger.assisted.AssistedInject import im.vector.app.AppStateHandler import im.vector.app.core.di.MavericksAssistedViewModelFactory import im.vector.app.core.di.hiltMavericksViewModelFactory -import im.vector.app.core.extensions.exhaustive import im.vector.app.core.platform.VectorViewModel import im.vector.app.features.analytics.AnalyticsTracker import im.vector.app.features.analytics.plan.CreatedRoom @@ -41,6 +40,7 @@ import org.matrix.android.sdk.api.extensions.orFalse import org.matrix.android.sdk.api.extensions.tryOrNull import org.matrix.android.sdk.api.raw.RawService import org.matrix.android.sdk.api.session.Session +import org.matrix.android.sdk.api.session.getRoomSummary import org.matrix.android.sdk.api.session.homeserver.HomeServerCapabilities import org.matrix.android.sdk.api.session.room.alias.RoomAliasError import org.matrix.android.sdk.api.session.room.failure.CreateRoomFailure @@ -75,7 +75,8 @@ class CreateRoomViewModel @AssistedInject constructor( val parentSpaceId = initialState.parentSpaceId ?: appStateHandler.safeActiveSpaceId() - val restrictedSupport = session.getHomeServerCapabilities().isFeatureSupported(HomeServerCapabilities.ROOM_CAP_RESTRICTED) + val restrictedSupport = session.homeServerCapabilitiesService().getHomeServerCapabilities() + .isFeatureSupported(HomeServerCapabilities.ROOM_CAP_RESTRICTED) val createRestricted = restrictedSupport == HomeServerCapabilities.RoomCapabilitySupport.SUPPORTED val defaultJoinRules = if (parentSpaceId != null && createRestricted) { @@ -138,7 +139,7 @@ class CreateRoomViewModel @AssistedInject constructor( CreateRoomAction.Reset -> doReset() CreateRoomAction.ToggleShowAdvanced -> toggleShowAdvanced() is CreateRoomAction.DisableFederation -> disableFederation(action) - }.exhaustive + } } private fun disableFederation(action: CreateRoomAction.DisableFederation) { @@ -266,7 +267,7 @@ class CreateRoomViewModel @AssistedInject constructor( RoomJoinRules.RESTRICTED -> { state.parentSpaceId?.let { featurePreset = RestrictedRoomPreset( - session.getHomeServerCapabilities(), + session.homeServerCapabilitiesService().getHomeServerCapabilities(), listOf(RoomJoinRulesAllowEntry.restrictedToRoom(state.parentSpaceId)) ) } @@ -281,7 +282,7 @@ class CreateRoomViewModel @AssistedInject constructor( // Preset preset = CreateRoomPreset.PRESET_PRIVATE_CHAT } - }.exhaustive + } // Disabling federation disableFederation = state.disableFederation @@ -298,7 +299,7 @@ class CreateRoomViewModel @AssistedInject constructor( // TODO: Should this be non-cancellable? viewModelScope.launch { - runCatching { session.createRoom(createRoomParams) }.fold( + runCatching { session.roomService().createRoom(createRoomParams) }.fold( { roomId -> analyticsTracker.capture(CreatedRoom(isDM = createRoomParams.isDirect.orFalse())) if (state.parentSpaceId != null) { diff --git a/vector/src/main/java/im/vector/app/features/roomdirectory/picker/RoomDirectoryPickerController.kt b/vector/src/main/java/im/vector/app/features/roomdirectory/picker/RoomDirectoryPickerController.kt index 08e044630d..7d121d1ff4 100644 --- a/vector/src/main/java/im/vector/app/features/roomdirectory/picker/RoomDirectoryPickerController.kt +++ b/vector/src/main/java/im/vector/app/features/roomdirectory/picker/RoomDirectoryPickerController.kt @@ -22,7 +22,6 @@ import android.view.inputmethod.EditorInfo import android.widget.TextView import com.airbnb.epoxy.TypedEpoxyController import com.airbnb.mvrx.Fail -import com.airbnb.mvrx.Incomplete import com.airbnb.mvrx.Loading import com.airbnb.mvrx.Success import com.airbnb.mvrx.Uninitialized @@ -60,7 +59,7 @@ class RoomDirectoryPickerController @Inject constructor( val host = this when (val asyncThirdPartyProtocol = data.asyncThirdPartyRequest) { - is Success -> { + is Success -> { data.directories.join( each = { _, roomDirectoryServer -> buildDirectory(roomDirectoryServer) }, between = { idx, _ -> buildDivider(idx) } @@ -71,12 +70,13 @@ class RoomDirectoryPickerController @Inject constructor( heightInPx(host.dimensionConverter.dpToPx(16)) } } - is Incomplete -> { + Uninitialized, + is Loading -> { loadingItem { id("loading") } } - is Fail -> { + is Fail -> { errorWithRetryItem { id("error") text(host.errorFormatter.toHumanReadable(asyncThirdPartyProtocol.error)) diff --git a/vector/src/main/java/im/vector/app/features/roomdirectory/picker/RoomDirectoryPickerViewModel.kt b/vector/src/main/java/im/vector/app/features/roomdirectory/picker/RoomDirectoryPickerViewModel.kt index a5673e78a2..48b6997bd4 100644 --- a/vector/src/main/java/im/vector/app/features/roomdirectory/picker/RoomDirectoryPickerViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/roomdirectory/picker/RoomDirectoryPickerViewModel.kt @@ -27,7 +27,6 @@ import dagger.assisted.AssistedInject import im.vector.app.R import im.vector.app.core.di.MavericksAssistedViewModelFactory import im.vector.app.core.di.hiltMavericksViewModelFactory -import im.vector.app.core.extensions.exhaustive import im.vector.app.core.platform.EmptyViewEvents import im.vector.app.core.platform.VectorViewModel import im.vector.app.core.resources.StringProvider @@ -104,7 +103,7 @@ class RoomDirectoryPickerViewModel @AssistedInject constructor( is RoomDirectoryPickerAction.SetServerUrl -> handleSetServerUrl(action) RoomDirectoryPickerAction.Submit -> handleSubmit() is RoomDirectoryPickerAction.RemoveServer -> handleRemoveServer(action) - }.exhaustive + } } private fun handleEnterEditMode() { @@ -153,7 +152,7 @@ class RoomDirectoryPickerViewModel @AssistedInject constructor( copy(addServerAsync = Loading()) } try { - session.getPublicRooms( + session.roomDirectoryService().getPublicRooms( server = enteredServer, publicRoomsParams = PublicRoomsParams(limit = 1) ) diff --git a/vector/src/main/java/im/vector/app/features/roomdirectory/roompreview/RoomPreviewNoPreviewFragment.kt b/vector/src/main/java/im/vector/app/features/roomdirectory/roompreview/RoomPreviewNoPreviewFragment.kt index 6d0195fae3..baa6d0add1 100644 --- a/vector/src/main/java/im/vector/app/features/roomdirectory/roompreview/RoomPreviewNoPreviewFragment.kt +++ b/vector/src/main/java/im/vector/app/features/roomdirectory/roompreview/RoomPreviewNoPreviewFragment.kt @@ -37,6 +37,7 @@ import im.vector.app.core.platform.VectorBaseFragment import im.vector.app.core.utils.styleMatchingText import im.vector.app.core.utils.tappableMatchingText import im.vector.app.databinding.FragmentRoomPreviewNoPreviewBinding +import im.vector.app.features.analytics.plan.MobileScreen import im.vector.app.features.home.AvatarRenderer import im.vector.app.features.navigation.Navigator import im.vector.app.features.roomdirectory.JoinState @@ -69,6 +70,11 @@ class RoomPreviewNoPreviewFragment @Inject constructor( views.roomPreviewNoPreviewJoin.commonClicked = { roomPreviewViewModel.handle(RoomPreviewAction.Join) } } + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + analyticsScreenName = MobileScreen.ScreenName.RoomPreview + } + override fun invalidate() = withState(roomPreviewViewModel) { state -> views.roomPreviewNoPreviewJoin.render( @@ -110,8 +116,7 @@ class RoomPreviewNoPreviewFragment @Inject constructor( PeekingState.FOUND -> { // show join buttons views.roomPreviewNoPreviewJoin.isVisible = true - renderState(bestName, state.matrixItem(), state.roomTopic - /**, state.roomType*/) + renderState(bestName, state.matrixItem(), state.roomTopic) if (state.fromEmailInvite != null && !state.isEmailBoundToAccount) { views.roomPreviewNoPreviewLabel.text = span { @@ -152,15 +157,13 @@ class RoomPreviewNoPreviewFragment @Inject constructor( views.roomPreviewNoPreviewJoin.isVisible = true views.roomPreviewNoPreviewLabel.isVisible = true views.roomPreviewNoPreviewLabel.setText(R.string.room_preview_no_preview_join) - renderState(bestName, state.matrixItem().takeIf { state.roomAlias != null }, state.roomTopic - /**, state.roomType*/) + renderState(bestName, state.matrixItem().takeIf { state.roomAlias != null }, state.roomTopic) } else -> { views.roomPreviewNoPreviewJoin.isVisible = false views.roomPreviewNoPreviewLabel.isVisible = true views.roomPreviewNoPreviewLabel.setText(R.string.room_preview_not_found) - renderState(bestName, null, state.roomTopic - /**, state.roomType*/) + renderState(bestName, null, state.roomTopic) } } } @@ -168,16 +171,13 @@ class RoomPreviewNoPreviewFragment @Inject constructor( // Render with initial state, no peeking views.roomPreviewPeekingProgress.isVisible = false views.roomPreviewNoPreviewJoin.isVisible = true - renderState(bestName, state.matrixItem(), state.roomTopic - /**, state.roomType*/) + renderState(bestName, state.matrixItem(), state.roomTopic) views.roomPreviewNoPreviewLabel.isVisible = false } } } - private fun renderState(roomName: String, matrixItem: MatrixItem?, topic: String? - /**, roomType: String?*/ - ) { + private fun renderState(roomName: String, matrixItem: MatrixItem?, topic: String?) { // Toolbar if (matrixItem != null) { views.roomPreviewNoPreviewToolbarAvatar.isVisible = true diff --git a/vector/src/main/java/im/vector/app/features/roomdirectory/roompreview/RoomPreviewViewModel.kt b/vector/src/main/java/im/vector/app/features/roomdirectory/roompreview/RoomPreviewViewModel.kt index 42bec8c8b3..88bfc32de9 100644 --- a/vector/src/main/java/im/vector/app/features/roomdirectory/roompreview/RoomPreviewViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/roomdirectory/roompreview/RoomPreviewViewModel.kt @@ -24,7 +24,6 @@ import dagger.assisted.AssistedFactory import dagger.assisted.AssistedInject import im.vector.app.core.di.MavericksAssistedViewModelFactory import im.vector.app.core.di.hiltMavericksViewModelFactory -import im.vector.app.core.extensions.exhaustive import im.vector.app.core.platform.EmptyViewEvents import im.vector.app.core.platform.VectorViewModel import im.vector.app.features.analytics.AnalyticsTracker @@ -73,6 +72,7 @@ class RoomPreviewViewModel @AssistedInject constructor( // we might want to check if the mail is bound to this account? // if it is the invite val threePids = session + .profileService() .getThreePids() .filterIsInstance() @@ -108,7 +108,7 @@ class RoomPreviewViewModel @AssistedInject constructor( } viewModelScope.launch(Dispatchers.IO) { val peekResult = tryOrNull { - session.peekRoom(initialState.roomAlias ?: initialState.roomId) + session.roomService().peekRoom(initialState.roomAlias ?: initialState.roomId) } when (peekResult) { @@ -204,7 +204,7 @@ class RoomPreviewViewModel @AssistedInject constructor( when (action) { is RoomPreviewAction.Join -> handleJoinRoom() RoomPreviewAction.JoinThirdParty -> handleJoinRoomThirdParty() - }.exhaustive + } } private fun handleJoinRoomThirdParty() = withState { state -> @@ -227,7 +227,7 @@ class RoomPreviewViewModel @AssistedInject constructor( state.fromEmailInvite?.privateKey ?: "" ) - session.joinRoom(state.roomId, reason = null, thirdPartySigned) + session.roomService().joinRoom(state.roomId, reason = null, thirdPartySigned) } catch (failure: Throwable) { setState { copy( @@ -247,12 +247,12 @@ class RoomPreviewViewModel @AssistedInject constructor( } viewModelScope.launch { try { - session.joinRoom(state.roomId, viaServers = state.homeServers) + session.roomService().joinRoom(state.roomId, viaServers = state.homeServers) analyticsTracker.capture(JoinedRoom( // Always false in this case (?) isDM = false, isSpace = false, - roomSize = state.numJoinMembers.toAnalyticsRoomSize() + roomSize = state.numJoinMembers.toAnalyticsRoomSize(), )) // We do not update the joiningRoomsIds here, because, the room is not joined yet regarding the sync data. // Instead, we wait for the room to be joined diff --git a/vector/src/main/java/im/vector/app/features/roommemberprofile/RoomMemberProfileFragment.kt b/vector/src/main/java/im/vector/app/features/roommemberprofile/RoomMemberProfileFragment.kt index 7e919fb663..760bbe9353 100644 --- a/vector/src/main/java/im/vector/app/features/roommemberprofile/RoomMemberProfileFragment.kt +++ b/vector/src/main/java/im/vector/app/features/roommemberprofile/RoomMemberProfileFragment.kt @@ -25,8 +25,9 @@ import android.view.View import android.view.ViewGroup import androidx.core.view.isVisible import com.airbnb.mvrx.Fail -import com.airbnb.mvrx.Incomplete +import com.airbnb.mvrx.Loading import com.airbnb.mvrx.Success +import com.airbnb.mvrx.Uninitialized import com.airbnb.mvrx.args import com.airbnb.mvrx.fragmentViewModel import com.airbnb.mvrx.withState @@ -38,7 +39,6 @@ import im.vector.app.core.dialogs.ConfirmationDialogBuilder import im.vector.app.core.extensions.cleanup import im.vector.app.core.extensions.configureWith import im.vector.app.core.extensions.copyOnLongClick -import im.vector.app.core.extensions.exhaustive import im.vector.app.core.extensions.setTextOrHide import im.vector.app.core.platform.StateView import im.vector.app.core.platform.VectorBaseFragment @@ -57,7 +57,7 @@ import im.vector.app.features.home.room.detail.timeline.helper.MatrixItemColorPr import im.vector.app.features.roommemberprofile.devices.DeviceListBottomSheet import im.vector.app.features.roommemberprofile.powerlevel.EditPowerLevelDialogs import kotlinx.parcelize.Parcelize -import org.matrix.android.sdk.api.crypto.RoomEncryptionTrustLevel +import org.matrix.android.sdk.api.session.crypto.model.RoomEncryptionTrustLevel import org.matrix.android.sdk.api.session.room.powerlevels.Role import org.matrix.android.sdk.api.util.MatrixItem import javax.inject.Inject @@ -133,7 +133,7 @@ class RoomMemberProfileFragment @Inject constructor( is RoomMemberProfileViewEvents.OnBanActionSuccess -> Unit is RoomMemberProfileViewEvents.OnIgnoreActionSuccess -> Unit is RoomMemberProfileViewEvents.OnInviteActionSuccess -> Unit - }.exhaustive + } } setupLongClicks() } @@ -198,18 +198,19 @@ class RoomMemberProfileFragment @Inject constructor( override fun invalidate() = withState(viewModel) { state -> when (val asyncUserMatrixItem = state.userMatrixItem) { - is Incomplete -> { + Uninitialized, + is Loading -> { views.matrixProfileToolbarTitleView.text = state.userId avatarRenderer.render(MatrixItem.UserItem(state.userId, null, null), views.matrixProfileToolbarAvatarImageView) headerViews.memberProfileStateView.state = StateView.State.Loading } - is Fail -> { + is Fail -> { avatarRenderer.render(MatrixItem.UserItem(state.userId, null, null), views.matrixProfileToolbarAvatarImageView) views.matrixProfileToolbarTitleView.text = state.userId val failureMessage = errorFormatter.toHumanReadable(asyncUserMatrixItem.error) headerViews.memberProfileStateView.state = StateView.State.Error(failureMessage) } - is Success -> { + is Success -> { val userMatrixItem = asyncUserMatrixItem() headerViews.memberProfileStateView.state = StateView.State.Content headerViews.memberProfileIdView.text = userMatrixItem.id diff --git a/vector/src/main/java/im/vector/app/features/roommemberprofile/RoomMemberProfileViewModel.kt b/vector/src/main/java/im/vector/app/features/roommemberprofile/RoomMemberProfileViewModel.kt index a79a9f4c1d..f440043d6e 100644 --- a/vector/src/main/java/im/vector/app/features/roommemberprofile/RoomMemberProfileViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/roommemberprofile/RoomMemberProfileViewModel.kt @@ -28,7 +28,6 @@ import dagger.assisted.AssistedInject import im.vector.app.R import im.vector.app.core.di.MavericksAssistedViewModelFactory import im.vector.app.core.di.hiltMavericksViewModelFactory -import im.vector.app.core.extensions.exhaustive import im.vector.app.core.mvrx.runCatchingToAsync import im.vector.app.core.platform.VectorViewModel import im.vector.app.core.resources.StringProvider @@ -49,7 +48,7 @@ import org.matrix.android.sdk.api.session.accountdata.UserAccountDataTypes import org.matrix.android.sdk.api.session.events.model.EventType import org.matrix.android.sdk.api.session.events.model.toContent import org.matrix.android.sdk.api.session.events.model.toModel -import org.matrix.android.sdk.api.session.profile.ProfileService +import org.matrix.android.sdk.api.session.getRoom import org.matrix.android.sdk.api.session.room.Room import org.matrix.android.sdk.api.session.room.members.roomMemberQueryParams import org.matrix.android.sdk.api.session.room.model.Membership @@ -57,7 +56,7 @@ import org.matrix.android.sdk.api.session.room.model.RoomEncryptionAlgorithm import org.matrix.android.sdk.api.session.room.model.RoomType 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.util.MatrixItem +import org.matrix.android.sdk.api.session.user.model.User import org.matrix.android.sdk.api.util.toMatrixItem import org.matrix.android.sdk.api.util.toOptional import org.matrix.android.sdk.flow.flow @@ -114,8 +113,8 @@ class RoomMemberProfileViewModel @AssistedInject constructor( session.flow().liveUserCryptoDevices(initialState.userId) .map { Pair( - it.fold(true, { prev, dev -> prev && dev.isVerified }), - it.fold(true, { prev, dev -> prev && (dev.trustLevel?.crossSigningVerified == true) }) + it.fold(true) { prev, dev -> prev && dev.isVerified }, + it.fold(true) { prev, dev -> prev && (dev.trustLevel?.crossSigningVerified == true) } ) } .execute { it -> @@ -170,7 +169,7 @@ class RoomMemberProfileViewModel @AssistedInject constructor( RoomMemberProfileAction.InviteUser -> handleInviteAction() is RoomMemberProfileAction.SetUserColorOverride -> handleSetUserColorOverride(action) is RoomMemberProfileAction.OpenOrCreateDm -> handleOpenOrCreateDm(action) - }.exhaustive + } } private fun handleOpenOrCreateDm(action: RoomMemberProfileAction.OpenOrCreateDm) { @@ -329,14 +328,10 @@ class RoomMemberProfileViewModel @AssistedInject constructor( private suspend fun fetchProfileInfo() { val result = runCatchingToAsync { - session.getProfile(initialState.userId) - .let { - MatrixItem.UserItem( - id = initialState.userId, - displayName = it[ProfileService.DISPLAY_NAME_KEY] as? String, - avatarUrl = it[ProfileService.AVATAR_URL_KEY] as? String - ) - } + session.profileService() + .getProfile(initialState.userId) + .let { User.fromJson(initialState.userId, it) } + .toMatrixItem() } setState { @@ -393,9 +388,9 @@ class RoomMemberProfileViewModel @AssistedInject constructor( viewModelScope.launch { val event = try { if (isIgnored) { - session.unIgnoreUserIds(listOf(state.userId)) + session.userService().unIgnoreUserIds(listOf(state.userId)) } else { - session.ignoreUserIds(listOf(state.userId)) + session.userService().ignoreUserIds(listOf(state.userId)) } RoomMemberProfileViewEvents.OnIgnoreActionSuccess } catch (failure: Throwable) { diff --git a/vector/src/main/java/im/vector/app/features/roommemberprofile/devices/DeviceListAction.kt b/vector/src/main/java/im/vector/app/features/roommemberprofile/devices/DeviceListAction.kt index fc28b68b79..c269a4166a 100644 --- a/vector/src/main/java/im/vector/app/features/roommemberprofile/devices/DeviceListAction.kt +++ b/vector/src/main/java/im/vector/app/features/roommemberprofile/devices/DeviceListAction.kt @@ -17,7 +17,7 @@ package im.vector.app.features.roommemberprofile.devices import im.vector.app.core.platform.VectorViewModelAction -import org.matrix.android.sdk.internal.crypto.model.CryptoDeviceInfo +import org.matrix.android.sdk.api.session.crypto.model.CryptoDeviceInfo sealed class DeviceListAction : VectorViewModelAction { data class SelectDevice(val device: CryptoDeviceInfo) : DeviceListAction() diff --git a/vector/src/main/java/im/vector/app/features/roommemberprofile/devices/DeviceListBottomSheet.kt b/vector/src/main/java/im/vector/app/features/roommemberprofile/devices/DeviceListBottomSheet.kt index bb2317b59c..8df0b3ffd5 100644 --- a/vector/src/main/java/im/vector/app/features/roommemberprofile/devices/DeviceListBottomSheet.kt +++ b/vector/src/main/java/im/vector/app/features/roommemberprofile/devices/DeviceListBottomSheet.kt @@ -29,7 +29,6 @@ import com.airbnb.mvrx.withState import dagger.hilt.android.AndroidEntryPoint import im.vector.app.R import im.vector.app.core.extensions.commitTransaction -import im.vector.app.core.extensions.exhaustive import im.vector.app.core.platform.VectorBaseBottomSheetDialogFragment import im.vector.app.databinding.BottomSheetWithFragmentsBinding import im.vector.app.features.crypto.verification.VerificationBottomSheet @@ -57,7 +56,7 @@ class DeviceListBottomSheet : transactionId = it.txID ).show(requireActivity().supportFragmentManager, "REQPOP") } - }.exhaustive + } } } diff --git a/vector/src/main/java/im/vector/app/features/roommemberprofile/devices/DeviceListBottomSheetViewModel.kt b/vector/src/main/java/im/vector/app/features/roommemberprofile/devices/DeviceListBottomSheetViewModel.kt index d2491237ca..19040a1fde 100644 --- a/vector/src/main/java/im/vector/app/features/roommemberprofile/devices/DeviceListBottomSheetViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/roommemberprofile/devices/DeviceListBottomSheetViewModel.kt @@ -28,15 +28,15 @@ import dagger.hilt.EntryPoints import im.vector.app.core.di.MavericksAssistedViewModelFactory import im.vector.app.core.di.SingletonEntryPoint import im.vector.app.core.di.hiltMavericksViewModelFactory -import im.vector.app.core.extensions.exhaustive import im.vector.app.core.platform.VectorViewModel import org.matrix.android.sdk.api.session.Session import org.matrix.android.sdk.api.session.crypto.crosssigning.MXCrossSigningInfo +import org.matrix.android.sdk.api.session.crypto.model.CryptoDeviceInfo import org.matrix.android.sdk.api.session.crypto.verification.VerificationMethod +import org.matrix.android.sdk.api.session.getUser import org.matrix.android.sdk.api.util.MatrixItem import org.matrix.android.sdk.api.util.toMatrixItem import org.matrix.android.sdk.flow.flow -import org.matrix.android.sdk.internal.crypto.model.CryptoDeviceInfo data class DeviceListViewState( val userId: String, @@ -94,7 +94,7 @@ class DeviceListBottomSheetViewModel @AssistedInject constructor(@Assisted priva is DeviceListAction.SelectDevice -> selectDevice(action) is DeviceListAction.DeselectDevice -> deselectDevice() is DeviceListAction.ManuallyVerify -> manuallyVerify(action) - }.exhaustive + } } private fun refreshSelectedId() = withState { state -> diff --git a/vector/src/main/java/im/vector/app/features/roommemberprofile/devices/DeviceListEpoxyController.kt b/vector/src/main/java/im/vector/app/features/roommemberprofile/devices/DeviceListEpoxyController.kt index 3bfb210f8d..6a9d053d2a 100644 --- a/vector/src/main/java/im/vector/app/features/roommemberprofile/devices/DeviceListEpoxyController.kt +++ b/vector/src/main/java/im/vector/app/features/roommemberprofile/devices/DeviceListEpoxyController.kt @@ -34,7 +34,7 @@ import im.vector.app.core.utils.DimensionConverter import im.vector.app.features.settings.VectorPreferences import im.vector.lib.core.utils.epoxy.charsequence.toEpoxyCharSequence import me.gujun.android.span.span -import org.matrix.android.sdk.internal.crypto.model.CryptoDeviceInfo +import org.matrix.android.sdk.api.session.crypto.model.CryptoDeviceInfo import javax.inject.Inject class DeviceListEpoxyController @Inject constructor(private val stringProvider: StringProvider, diff --git a/vector/src/main/java/im/vector/app/features/roommemberprofile/devices/DeviceListFragment.kt b/vector/src/main/java/im/vector/app/features/roommemberprofile/devices/DeviceListFragment.kt index dbf1f19595..48a8a819bc 100644 --- a/vector/src/main/java/im/vector/app/features/roommemberprofile/devices/DeviceListFragment.kt +++ b/vector/src/main/java/im/vector/app/features/roommemberprofile/devices/DeviceListFragment.kt @@ -27,7 +27,7 @@ import im.vector.app.core.extensions.configureWith import im.vector.app.core.platform.VectorBaseFragment import im.vector.app.core.utils.DimensionConverter import im.vector.app.databinding.BottomSheetGenericListBinding -import org.matrix.android.sdk.internal.crypto.model.CryptoDeviceInfo +import org.matrix.android.sdk.api.session.crypto.model.CryptoDeviceInfo import javax.inject.Inject class DeviceListFragment @Inject constructor( diff --git a/vector/src/main/java/im/vector/app/features/roommemberprofile/devices/DeviceTrustInfoActionFragment.kt b/vector/src/main/java/im/vector/app/features/roommemberprofile/devices/DeviceTrustInfoActionFragment.kt index f82ecb6ddf..a733197372 100644 --- a/vector/src/main/java/im/vector/app/features/roommemberprofile/devices/DeviceTrustInfoActionFragment.kt +++ b/vector/src/main/java/im/vector/app/features/roommemberprofile/devices/DeviceTrustInfoActionFragment.kt @@ -27,7 +27,7 @@ import im.vector.app.core.extensions.configureWith import im.vector.app.core.platform.VectorBaseFragment import im.vector.app.core.utils.DimensionConverter import im.vector.app.databinding.BottomSheetGenericListBinding -import org.matrix.android.sdk.internal.crypto.model.CryptoDeviceInfo +import org.matrix.android.sdk.api.session.crypto.model.CryptoDeviceInfo import javax.inject.Inject class DeviceTrustInfoActionFragment @Inject constructor( diff --git a/vector/src/main/java/im/vector/app/features/roommemberprofile/devices/DeviceTrustInfoEpoxyController.kt b/vector/src/main/java/im/vector/app/features/roommemberprofile/devices/DeviceTrustInfoEpoxyController.kt index c7f6e64f5c..c3991cef99 100644 --- a/vector/src/main/java/im/vector/app/features/roommemberprofile/devices/DeviceTrustInfoEpoxyController.kt +++ b/vector/src/main/java/im/vector/app/features/roommemberprofile/devices/DeviceTrustInfoEpoxyController.kt @@ -29,14 +29,14 @@ import im.vector.app.features.crypto.verification.epoxy.bottomSheetVerificationA import im.vector.app.features.settings.VectorPreferences import im.vector.lib.core.utils.epoxy.charsequence.toEpoxyCharSequence import me.gujun.android.span.span -import org.matrix.android.sdk.internal.crypto.model.CryptoDeviceInfo +import org.matrix.android.sdk.api.session.crypto.model.CryptoDeviceInfo import javax.inject.Inject class DeviceTrustInfoEpoxyController @Inject constructor(private val stringProvider: StringProvider, private val colorProvider: ColorProvider, private val dimensionConverter: DimensionConverter, private val vectorPreferences: VectorPreferences) : - TypedEpoxyController() { + TypedEpoxyController() { interface InteractionListener { fun onVerifyManually(device: CryptoDeviceInfo) diff --git a/vector/src/main/java/im/vector/app/features/roomprofile/RoomProfileActivity.kt b/vector/src/main/java/im/vector/app/features/roomprofile/RoomProfileActivity.kt index 4c6d2ed2e3..29a2f73c78 100644 --- a/vector/src/main/java/im/vector/app/features/roomprofile/RoomProfileActivity.kt +++ b/vector/src/main/java/im/vector/app/features/roomprofile/RoomProfileActivity.kt @@ -26,7 +26,6 @@ import com.airbnb.mvrx.viewModel import dagger.hilt.android.AndroidEntryPoint import im.vector.app.core.extensions.addFragment import im.vector.app.core.extensions.addFragmentToBackstack -import im.vector.app.core.extensions.exhaustive import im.vector.app.core.platform.VectorBaseActivity import im.vector.app.databinding.ActivitySimpleBinding import im.vector.app.features.home.room.detail.RoomDetailPendingActionStore @@ -85,24 +84,24 @@ class RoomProfileActivity : addFragment(views.simpleFragmentContainer, RoomProfileFragment::class.java, roomProfileArgs) addFragmentToBackstack(views.simpleFragmentContainer, RoomSettingsFragment::class.java, roomProfileArgs) } - EXTRA_DIRECT_ACCESS_ROOM_MEMBERS -> { + EXTRA_DIRECT_ACCESS_ROOM_MEMBERS -> { addFragment(views.simpleFragmentContainer, RoomMemberListFragment::class.java, roomProfileArgs) } - else -> addFragment(views.simpleFragmentContainer, RoomProfileFragment::class.java, roomProfileArgs) + else -> addFragment(views.simpleFragmentContainer, RoomProfileFragment::class.java, roomProfileArgs) } } sharedActionViewModel .stream() .onEach { sharedAction -> when (sharedAction) { - RoomProfileSharedAction.OpenRoomMembers -> openRoomMembers() - RoomProfileSharedAction.OpenRoomSettings -> openRoomSettings() - RoomProfileSharedAction.OpenRoomAliasesSettings -> openRoomAlias() - RoomProfileSharedAction.OpenRoomPermissionsSettings -> openRoomPermissions() - RoomProfileSharedAction.OpenRoomUploads -> openRoomUploads() + RoomProfileSharedAction.OpenRoomMembers -> openRoomMembers() + RoomProfileSharedAction.OpenRoomSettings -> openRoomSettings() + RoomProfileSharedAction.OpenRoomAliasesSettings -> openRoomAlias() + RoomProfileSharedAction.OpenRoomPermissionsSettings -> openRoomPermissions() + RoomProfileSharedAction.OpenRoomUploads -> openRoomUploads() RoomProfileSharedAction.OpenBannedRoomMembers -> openBannedRoomMembers() RoomProfileSharedAction.OpenRoomNotificationSettings -> openRoomNotificationSettings() - }.exhaustive + } } .launchIn(lifecycleScope) diff --git a/vector/src/main/java/im/vector/app/features/roomprofile/RoomProfileController.kt b/vector/src/main/java/im/vector/app/features/roomprofile/RoomProfileController.kt index e4630dae3f..372b4e5a70 100644 --- a/vector/src/main/java/im/vector/app/features/roomprofile/RoomProfileController.kt +++ b/vector/src/main/java/im/vector/app/features/roomprofile/RoomProfileController.kt @@ -34,7 +34,7 @@ import im.vector.app.features.settings.VectorPreferences import im.vector.lib.core.utils.epoxy.charsequence.toEpoxyCharSequence import me.gujun.android.span.image import me.gujun.android.span.span -import org.matrix.android.sdk.api.crypto.RoomEncryptionTrustLevel +import org.matrix.android.sdk.api.session.crypto.model.RoomEncryptionTrustLevel import org.matrix.android.sdk.api.session.room.model.RoomEncryptionAlgorithm import org.matrix.android.sdk.api.session.room.model.RoomSummary import javax.inject.Inject diff --git a/vector/src/main/java/im/vector/app/features/roomprofile/RoomProfileFragment.kt b/vector/src/main/java/im/vector/app/features/roomprofile/RoomProfileFragment.kt index b13ef2a5d1..3944066584 100644 --- a/vector/src/main/java/im/vector/app/features/roomprofile/RoomProfileFragment.kt +++ b/vector/src/main/java/im/vector/app/features/roomprofile/RoomProfileFragment.kt @@ -37,13 +37,13 @@ import im.vector.app.core.animations.MatrixItemAppBarStateChangeListener import im.vector.app.core.extensions.cleanup import im.vector.app.core.extensions.configureWith import im.vector.app.core.extensions.copyOnLongClick -import im.vector.app.core.extensions.exhaustive import im.vector.app.core.extensions.setTextOrHide import im.vector.app.core.platform.VectorBaseFragment import im.vector.app.core.utils.copyToClipboard import im.vector.app.core.utils.startSharePlainTextIntent import im.vector.app.databinding.FragmentMatrixProfileBinding import im.vector.app.databinding.ViewStubRoomProfileHeaderBinding +import im.vector.app.features.analytics.plan.Interaction import im.vector.app.features.analytics.plan.MobileScreen import im.vector.app.features.home.AvatarRenderer import im.vector.app.features.home.room.detail.RoomDetailPendingAction @@ -54,7 +54,6 @@ import im.vector.app.features.home.room.list.actions.RoomListQuickActionsSharedA import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.onEach import kotlinx.parcelize.Parcelize -import org.matrix.android.sdk.api.MatrixConfiguration import org.matrix.android.sdk.api.session.room.notification.RoomNotificationState import org.matrix.android.sdk.api.util.toMatrixItem import timber.log.Timber @@ -69,7 +68,6 @@ class RoomProfileFragment @Inject constructor( private val roomProfileController: RoomProfileController, private val avatarRenderer: AvatarRenderer, private val roomDetailPendingActionStore: RoomDetailPendingActionStore, - private val matrixConfiguration: MatrixConfiguration ) : VectorBaseFragment(), RoomProfileController.Callback { @@ -91,7 +89,7 @@ class RoomProfileFragment @Inject constructor( override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) - analyticsScreenName = MobileScreen.ScreenName.RoomSettings + analyticsScreenName = MobileScreen.ScreenName.RoomDetails setFragmentResultListener(MigrateRoomBottomSheet.REQUEST_KEY) { _, bundle -> bundle.getString(MigrateRoomBottomSheet.BUNDLE_KEY_REPLACEMENT_ROOM)?.let { replacementRoomId -> roomDetailPendingActionStore.data = RoomDetailPendingAction.OpenRoom(replacementRoomId, closeCurrentRoom = true) @@ -127,7 +125,7 @@ class RoomProfileFragment @Inject constructor( is RoomProfileViewEvents.ShareRoomProfile -> onShareRoomProfile(it.permalink) is RoomProfileViewEvents.OnShortcutReady -> addShortcut(it) RoomProfileViewEvents.DismissLoading -> dismissLoadingDialog() - }.exhaustive + } } roomListQuickActionsSharedActionViewModel .stream() @@ -224,7 +222,7 @@ class RoomProfileFragment @Inject constructor( avatarRenderer.render(matrixItem, views.matrixProfileToolbarAvatarImageView) headerViews.roomProfileDecorationImageView.render(it.roomEncryptionTrustLevel) views.matrixProfileDecorationToolbarAvatarImageView.render(it.roomEncryptionTrustLevel) - headerViews.roomProfilePresenceImageView.render(it.isDirect && matrixConfiguration.presenceSyncEnabled, it.directUserPresence) + headerViews.roomProfilePresenceImageView.render(it.isDirect, it.directUserPresence) headerViews.roomProfilePublicImageView.isVisible = it.isPublic && !it.isDirect } } @@ -271,6 +269,11 @@ class RoomProfileFragment @Inject constructor( override fun createShortcut() { // Ask the view model to prepare it... roomProfileViewModel.handle(RoomProfileAction.CreateShortcut) + analyticsTracker.capture(Interaction( + index = null, + interactionType = null, + name = Interaction.Name.MobileRoomAddHome + )) } private fun addShortcut(onShortcutReady: RoomProfileViewEvents.OnShortcutReady) { diff --git a/vector/src/main/java/im/vector/app/features/roomprofile/RoomProfileViewModel.kt b/vector/src/main/java/im/vector/app/features/roomprofile/RoomProfileViewModel.kt index b7c7d24888..8453cc95cd 100644 --- a/vector/src/main/java/im/vector/app/features/roomprofile/RoomProfileViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/roomprofile/RoomProfileViewModel.kt @@ -24,9 +24,10 @@ import dagger.assisted.AssistedInject import im.vector.app.R import im.vector.app.core.di.MavericksAssistedViewModelFactory import im.vector.app.core.di.hiltMavericksViewModelFactory -import im.vector.app.core.extensions.exhaustive import im.vector.app.core.platform.VectorViewModel import im.vector.app.core.resources.StringProvider +import im.vector.app.features.analytics.AnalyticsTracker +import im.vector.app.features.analytics.plan.Interaction import im.vector.app.features.home.ShortcutCreator import im.vector.app.features.powerlevel.PowerLevelsFlowFactory import im.vector.app.features.session.coroutineScope @@ -38,6 +39,7 @@ import org.matrix.android.sdk.api.query.QueryStringValue import org.matrix.android.sdk.api.session.Session import org.matrix.android.sdk.api.session.events.model.EventType import org.matrix.android.sdk.api.session.events.model.toModel +import org.matrix.android.sdk.api.session.getRoom import org.matrix.android.sdk.api.session.room.members.roomMemberQueryParams import org.matrix.android.sdk.api.session.room.model.Membership import org.matrix.android.sdk.api.session.room.model.create.RoomCreateContent @@ -53,7 +55,8 @@ class RoomProfileViewModel @AssistedInject constructor( @Assisted private val initialState: RoomProfileViewState, private val stringProvider: StringProvider, private val shortcutCreator: ShortcutCreator, - private val session: Session + private val session: Session, + private val analyticsTracker: AnalyticsTracker ) : VectorViewModel(initialState) { @AssistedFactory @@ -137,7 +140,7 @@ class RoomProfileViewModel @AssistedInject constructor( is RoomProfileAction.ShareRoomProfile -> handleShareRoomProfile() RoomProfileAction.CreateShortcut -> handleCreateShortcut() RoomProfileAction.RestoreEncryptionState -> restoreEncryptionState() - }.exhaustive + } } fun isPublicRoom(): Boolean { @@ -186,7 +189,12 @@ class RoomProfileViewModel @AssistedInject constructor( _viewEvents.post(RoomProfileViewEvents.Loading(stringProvider.getString(R.string.room_profile_leaving_room))) viewModelScope.launch { try { - session.leaveRoom(room.roomId) + session.roomService().leaveRoom(room.roomId) + analyticsTracker.capture(Interaction( + index = null, + interactionType = null, + name = Interaction.Name.MobileRoomLeave + )) // Do nothing, we will be closing the room automatically when it will get back from sync } catch (failure: Throwable) { _viewEvents.post(RoomProfileViewEvents.Failure(failure)) diff --git a/vector/src/main/java/im/vector/app/features/roomprofile/alias/RoomAliasController.kt b/vector/src/main/java/im/vector/app/features/roomprofile/alias/RoomAliasController.kt index 03e6ab9984..fcf6bc3a47 100644 --- a/vector/src/main/java/im/vector/app/features/roomprofile/alias/RoomAliasController.kt +++ b/vector/src/main/java/im/vector/app/features/roomprofile/alias/RoomAliasController.kt @@ -199,12 +199,13 @@ class RoomAliasController @Inject constructor( } when (val localAliases = data.localAliases) { - is Uninitialized -> { + Uninitialized, + is Loading -> { loadingItem { id("loadingAliases") } } - is Success -> { + is Success -> { if (localAliases().isEmpty()) { settingsInfoItem { id("locEmpty") @@ -220,7 +221,7 @@ class RoomAliasController @Inject constructor( } } } - is Fail -> { + is Fail -> { errorWithRetryItem { id("alt_error") text(host.errorFormatter.toHumanReadable(localAliases.error)) diff --git a/vector/src/main/java/im/vector/app/features/roomprofile/alias/RoomAliasFragment.kt b/vector/src/main/java/im/vector/app/features/roomprofile/alias/RoomAliasFragment.kt index e48ce54e6c..d5eeedee74 100644 --- a/vector/src/main/java/im/vector/app/features/roomprofile/alias/RoomAliasFragment.kt +++ b/vector/src/main/java/im/vector/app/features/roomprofile/alias/RoomAliasFragment.kt @@ -29,11 +29,11 @@ import com.google.android.material.dialog.MaterialAlertDialogBuilder import im.vector.app.R import im.vector.app.core.extensions.cleanup import im.vector.app.core.extensions.configureWith -import im.vector.app.core.extensions.exhaustive import im.vector.app.core.platform.VectorBaseFragment import im.vector.app.core.utils.shareText import im.vector.app.core.utils.toast import im.vector.app.databinding.FragmentRoomSettingGenericBinding +import im.vector.app.features.analytics.plan.MobileScreen import im.vector.app.features.home.AvatarRenderer import im.vector.app.features.roomprofile.RoomProfileArgs import im.vector.app.features.roomprofile.alias.detail.RoomAliasBottomSheet @@ -62,6 +62,11 @@ class RoomAliasFragment @Inject constructor( return FragmentRoomSettingGenericBinding.inflate(inflater, container, false) } + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + analyticsScreenName = MobileScreen.ScreenName.RoomAddresses + } + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) sharedActionViewModel = activityViewModelProvider.get(RoomAliasBottomSheetSharedActionViewModel::class.java) @@ -77,7 +82,7 @@ class RoomAliasFragment @Inject constructor( when (it) { is RoomAliasViewEvents.Failure -> showFailure(it.throwable) RoomAliasViewEvents.Success -> showSuccess() - }.exhaustive + } } sharedActionViewModel diff --git a/vector/src/main/java/im/vector/app/features/roomprofile/alias/RoomAliasViewModel.kt b/vector/src/main/java/im/vector/app/features/roomprofile/alias/RoomAliasViewModel.kt index 19f600e5de..550af98df2 100644 --- a/vector/src/main/java/im/vector/app/features/roomprofile/alias/RoomAliasViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/roomprofile/alias/RoomAliasViewModel.kt @@ -26,7 +26,6 @@ import dagger.assisted.AssistedFactory import dagger.assisted.AssistedInject import im.vector.app.core.di.MavericksAssistedViewModelFactory import im.vector.app.core.di.hiltMavericksViewModelFactory -import im.vector.app.core.extensions.exhaustive import im.vector.app.core.platform.VectorViewModel import im.vector.app.features.powerlevel.PowerLevelsFlowFactory import kotlinx.coroutines.flow.launchIn @@ -37,6 +36,7 @@ import org.matrix.android.sdk.api.query.QueryStringValue import org.matrix.android.sdk.api.session.Session import org.matrix.android.sdk.api.session.events.model.EventType import org.matrix.android.sdk.api.session.events.model.toModel +import org.matrix.android.sdk.api.session.getRoom import org.matrix.android.sdk.api.session.room.model.RoomCanonicalAliasContent import org.matrix.android.sdk.api.session.room.powerlevels.PowerLevelsHelper import org.matrix.android.sdk.flow.flow @@ -73,7 +73,7 @@ class RoomAliasViewModel @AssistedInject constructor(@Assisted initialState: Roo } viewModelScope.launch { runCatching { - session.getRoomDirectoryVisibility(room.roomId) + session.roomDirectoryService().getRoomDirectoryVisibility(room.roomId) }.fold( { setState { @@ -190,7 +190,7 @@ class RoomAliasViewModel @AssistedInject constructor(@Assisted initialState: Roo is RoomAliasAction.RemoveLocalAlias -> handleRemoveLocalAlias(action) is RoomAliasAction.PublishAlias -> handlePublishAlias(action) RoomAliasAction.Retry -> handleRetry() - }.exhaustive + } } private fun handleRetry() = withState { state -> @@ -206,7 +206,7 @@ class RoomAliasViewModel @AssistedInject constructor(@Assisted initialState: Roo postLoading(true) viewModelScope.launch { runCatching { - session.setRoomDirectoryVisibility(room.roomId, action.roomDirectoryVisibility) + session.roomDirectoryService().setRoomDirectoryVisibility(room.roomId, action.roomDirectoryVisibility) }.fold( { setState { @@ -355,7 +355,7 @@ class RoomAliasViewModel @AssistedInject constructor(@Assisted initialState: Roo private fun handleRemoveLocalAlias(action: RoomAliasAction.RemoveLocalAlias) { postLoading(true) viewModelScope.launch { - runCatching { session.deleteRoomAlias(action.alias) } + runCatching { session.roomService().deleteRoomAlias(action.alias) } .onFailure { setState { copy(isLoading = false) diff --git a/vector/src/main/java/im/vector/app/features/roomprofile/alias/detail/RoomAliasBottomSheetSharedAction.kt b/vector/src/main/java/im/vector/app/features/roomprofile/alias/detail/RoomAliasBottomSheetSharedAction.kt index 7625972b05..d50c19635d 100644 --- a/vector/src/main/java/im/vector/app/features/roomprofile/alias/detail/RoomAliasBottomSheetSharedAction.kt +++ b/vector/src/main/java/im/vector/app/features/roomprofile/alias/detail/RoomAliasBottomSheetSharedAction.kt @@ -25,7 +25,7 @@ sealed class RoomAliasBottomSheetSharedAction( @StringRes val titleRes: Int, @DrawableRes val iconResId: Int = 0, val destructive: Boolean = false) : - VectorSharedAction { + VectorSharedAction { data class ShareAlias(val matrixTo: String) : RoomAliasBottomSheetSharedAction( R.string.action_share, diff --git a/vector/src/main/java/im/vector/app/features/roomprofile/banned/RoomBannedMemberListViewModel.kt b/vector/src/main/java/im/vector/app/features/roomprofile/banned/RoomBannedMemberListViewModel.kt index d7efc2fb79..3262d61650 100644 --- a/vector/src/main/java/im/vector/app/features/roomprofile/banned/RoomBannedMemberListViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/roomprofile/banned/RoomBannedMemberListViewModel.kt @@ -23,7 +23,6 @@ import dagger.assisted.AssistedInject import im.vector.app.R import im.vector.app.core.di.MavericksAssistedViewModelFactory import im.vector.app.core.di.hiltMavericksViewModelFactory -import im.vector.app.core.extensions.exhaustive import im.vector.app.core.platform.VectorViewModel import im.vector.app.core.resources.StringProvider import im.vector.app.features.powerlevel.PowerLevelsFlowFactory @@ -33,6 +32,7 @@ import org.matrix.android.sdk.api.query.QueryStringValue import org.matrix.android.sdk.api.session.Session import org.matrix.android.sdk.api.session.events.model.EventType import org.matrix.android.sdk.api.session.events.model.toModel +import org.matrix.android.sdk.api.session.getRoom import org.matrix.android.sdk.api.session.room.members.roomMemberQueryParams import org.matrix.android.sdk.api.session.room.model.Membership import org.matrix.android.sdk.api.session.room.model.RoomMemberContent @@ -84,7 +84,7 @@ class RoomBannedMemberListViewModel @AssistedInject constructor(@Assisted initia is RoomBannedMemberListAction.QueryInfo -> onQueryBanInfo(action.roomMemberSummary) is RoomBannedMemberListAction.UnBanUser -> unBanUser(action.roomMemberSummary) is RoomBannedMemberListAction.Filter -> handleFilter(action) - }.exhaustive + } } private fun handleFilter(action: RoomBannedMemberListAction.Filter) { diff --git a/vector/src/main/java/im/vector/app/features/roomprofile/members/RoomMemberListController.kt b/vector/src/main/java/im/vector/app/features/roomprofile/members/RoomMemberListController.kt index 6e0bb12642..ebcebfa470 100644 --- a/vector/src/main/java/im/vector/app/features/roomprofile/members/RoomMemberListController.kt +++ b/vector/src/main/java/im/vector/app/features/roomprofile/members/RoomMemberListController.kt @@ -27,7 +27,6 @@ import im.vector.app.core.resources.ColorProvider import im.vector.app.core.resources.StringProvider import im.vector.app.features.home.AvatarRenderer import me.gujun.android.span.span -import org.matrix.android.sdk.api.MatrixConfiguration 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.room.model.RoomMemberSummary @@ -40,8 +39,7 @@ class RoomMemberListController @Inject constructor( private val avatarRenderer: AvatarRenderer, private val stringProvider: StringProvider, private val colorProvider: ColorProvider, - private val roomMemberSummaryFilter: RoomMemberSummaryFilter, - private val matrixConfiguration: MatrixConfiguration + private val roomMemberSummaryFilter: RoomMemberSummaryFilter ) : TypedEpoxyController() { interface Callback { @@ -124,7 +122,6 @@ class RoomMemberListController @Inject constructor( host: RoomMemberListController, data: RoomMemberListViewState) { val powerLabel = stringProvider.getString(powerLevelCategory.titleRes) - val presenceSyncEnabled = matrixConfiguration.presenceSyncEnabled profileMatrixItemWithPowerLevelWithPresence { id(roomMember.userId) @@ -134,8 +131,9 @@ class RoomMemberListController @Inject constructor( clickListener { host.callback?.onRoomMemberClicked(roomMember) } - showPresence(presenceSyncEnabled) + showPresence(true) userPresence(roomMember.userPresence) + ignoredUser(roomMember.userId in data.ignoredUserIds) powerLevelLabel( span { span(powerLabel) { diff --git a/vector/src/main/java/im/vector/app/features/roomprofile/members/RoomMemberListFragment.kt b/vector/src/main/java/im/vector/app/features/roomprofile/members/RoomMemberListFragment.kt index d7a9ecf39b..c1e97f0416 100644 --- a/vector/src/main/java/im/vector/app/features/roomprofile/members/RoomMemberListFragment.kt +++ b/vector/src/main/java/im/vector/app/features/roomprofile/members/RoomMemberListFragment.kt @@ -32,6 +32,7 @@ import im.vector.app.core.extensions.cleanup import im.vector.app.core.extensions.configureWith import im.vector.app.core.platform.VectorBaseFragment import im.vector.app.databinding.FragmentRoomMemberListBinding +import im.vector.app.features.analytics.plan.MobileScreen import im.vector.app.features.home.AvatarRenderer import im.vector.app.features.roomprofile.RoomProfileArgs import org.matrix.android.sdk.api.session.events.model.Event @@ -54,6 +55,11 @@ class RoomMemberListFragment @Inject constructor( return FragmentRoomMemberListBinding.inflate(inflater, container, false) } + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + analyticsScreenName = MobileScreen.ScreenName.RoomMembers + } + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) roomMemberListController.callback = this diff --git a/vector/src/main/java/im/vector/app/features/roomprofile/members/RoomMemberListViewModel.kt b/vector/src/main/java/im/vector/app/features/roomprofile/members/RoomMemberListViewModel.kt index 0bbdd87f3e..f7e06fe444 100644 --- a/vector/src/main/java/im/vector/app/features/roomprofile/members/RoomMemberListViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/roomprofile/members/RoomMemberListViewModel.kt @@ -23,7 +23,6 @@ import dagger.assisted.AssistedFactory import dagger.assisted.AssistedInject import im.vector.app.core.di.MavericksAssistedViewModelFactory import im.vector.app.core.di.hiltMavericksViewModelFactory -import im.vector.app.core.extensions.exhaustive import im.vector.app.core.platform.EmptyViewEvents import im.vector.app.core.platform.VectorViewModel import im.vector.app.features.powerlevel.PowerLevelsFlowFactory @@ -34,12 +33,13 @@ import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.launch -import org.matrix.android.sdk.api.crypto.RoomEncryptionTrustLevel import org.matrix.android.sdk.api.extensions.orFalse import org.matrix.android.sdk.api.query.QueryStringValue import org.matrix.android.sdk.api.session.Session +import org.matrix.android.sdk.api.session.crypto.model.RoomEncryptionTrustLevel import org.matrix.android.sdk.api.session.events.model.EventType import org.matrix.android.sdk.api.session.events.model.toModel +import org.matrix.android.sdk.api.session.getRoom import org.matrix.android.sdk.api.session.room.members.roomMemberQueryParams import org.matrix.android.sdk.api.session.room.model.Membership import org.matrix.android.sdk.api.session.room.model.PowerLevelsContent @@ -70,6 +70,7 @@ class RoomMemberListViewModel @AssistedInject constructor(@Assisted initialState observeThirdPartyInvites() observeRoomSummary() observePowerLevel() + observeIgnoredUsers() } private fun observeRoomMemberSummaries() { @@ -149,6 +150,16 @@ class RoomMemberListViewModel @AssistedInject constructor(@Assisted initialState } } + private fun observeIgnoredUsers() { + session.flow() + .liveIgnoredUsers() + .execute { async -> + copy( + ignoredUserIds = async.invoke().orEmpty().map { it.userId } + ) + } + } + private fun buildRoomMemberSummaries(powerLevelsContent: PowerLevelsContent, roomMembers: List): RoomMemberSummaries { val admins = ArrayList() val moderators = ArrayList() @@ -181,7 +192,7 @@ class RoomMemberListViewModel @AssistedInject constructor(@Assisted initialState when (action) { is RoomMemberListAction.RevokeThreePidInvite -> handleRevokeThreePidInvite(action) is RoomMemberListAction.FilterMemberList -> handleFilterMemberList(action) - }.exhaustive + } } private fun handleRevokeThreePidInvite(action: RoomMemberListAction.RevokeThreePidInvite) { diff --git a/vector/src/main/java/im/vector/app/features/roomprofile/members/RoomMemberListViewState.kt b/vector/src/main/java/im/vector/app/features/roomprofile/members/RoomMemberListViewState.kt index d736260f10..47a89b523a 100644 --- a/vector/src/main/java/im/vector/app/features/roomprofile/members/RoomMemberListViewState.kt +++ b/vector/src/main/java/im/vector/app/features/roomprofile/members/RoomMemberListViewState.kt @@ -23,7 +23,7 @@ import com.airbnb.mvrx.Uninitialized import im.vector.app.R import im.vector.app.core.platform.GenericIdArgs import im.vector.app.features.roomprofile.RoomProfileArgs -import org.matrix.android.sdk.api.crypto.RoomEncryptionTrustLevel +import org.matrix.android.sdk.api.session.crypto.model.RoomEncryptionTrustLevel import org.matrix.android.sdk.api.session.events.model.Event import org.matrix.android.sdk.api.session.room.model.RoomMemberSummary import org.matrix.android.sdk.api.session.room.model.RoomSummary @@ -32,6 +32,7 @@ data class RoomMemberListViewState( val roomId: String, val roomSummary: Async = Uninitialized, val roomMemberSummaries: Async = Uninitialized, + val ignoredUserIds: List = emptyList(), val filter: String = "", val threePidInvites: Async> = Uninitialized, val trustLevelMap: Async> = Uninitialized, diff --git a/vector/src/main/java/im/vector/app/features/roomprofile/notifications/RoomNotificationSettingsController.kt b/vector/src/main/java/im/vector/app/features/roomprofile/notifications/RoomNotificationSettingsController.kt index 8f6e8f54c1..3ee0a00f28 100644 --- a/vector/src/main/java/im/vector/app/features/roomprofile/notifications/RoomNotificationSettingsController.kt +++ b/vector/src/main/java/im/vector/app/features/roomprofile/notifications/RoomNotificationSettingsController.kt @@ -42,7 +42,7 @@ class RoomNotificationSettingsController @Inject constructor() : TypedEpoxyContr id("roomNotificationSettingsHeader") textRes(R.string.room_settings_room_notifications_notify_me) } - data.notificationOptions.forEach { notificationState -> + data.notificationOptions.forEach { notificationState -> val title = titleForNotificationState(notificationState) radioButtonItem { id(notificationState.name) @@ -67,6 +67,6 @@ class RoomNotificationSettingsController @Inject constructor() : TypedEpoxyContr RoomNotificationState.ALL_MESSAGES_NOISY -> R.string.room_settings_all_messages RoomNotificationState.MENTIONS_ONLY -> R.string.room_settings_mention_and_keyword_only RoomNotificationState.MUTE -> R.string.room_settings_none - else -> null + else -> null } } diff --git a/vector/src/main/java/im/vector/app/features/roomprofile/notifications/RoomNotificationSettingsFragment.kt b/vector/src/main/java/im/vector/app/features/roomprofile/notifications/RoomNotificationSettingsFragment.kt index 320fdfd833..1bf392d9f8 100644 --- a/vector/src/main/java/im/vector/app/features/roomprofile/notifications/RoomNotificationSettingsFragment.kt +++ b/vector/src/main/java/im/vector/app/features/roomprofile/notifications/RoomNotificationSettingsFragment.kt @@ -28,6 +28,7 @@ import im.vector.app.core.extensions.cleanup import im.vector.app.core.extensions.configureWith import im.vector.app.core.platform.VectorBaseFragment import im.vector.app.databinding.FragmentRoomSettingGenericBinding +import im.vector.app.features.analytics.plan.MobileScreen import im.vector.app.features.home.AvatarRenderer import im.vector.app.features.settings.VectorSettingsActivity import org.matrix.android.sdk.api.session.room.notification.RoomNotificationState @@ -47,6 +48,11 @@ class RoomNotificationSettingsFragment @Inject constructor( return FragmentRoomSettingGenericBinding.inflate(inflater, container, false) } + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + analyticsScreenName = MobileScreen.ScreenName.RoomNotifications + } + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) setupToolbar(views.roomSettingsToolbar) diff --git a/vector/src/main/java/im/vector/app/features/roomprofile/notifications/RoomNotificationSettingsViewModel.kt b/vector/src/main/java/im/vector/app/features/roomprofile/notifications/RoomNotificationSettingsViewModel.kt index 26db6b001e..bef7a7872f 100644 --- a/vector/src/main/java/im/vector/app/features/roomprofile/notifications/RoomNotificationSettingsViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/roomprofile/notifications/RoomNotificationSettingsViewModel.kt @@ -26,6 +26,7 @@ import im.vector.app.core.di.hiltMavericksViewModelFactory import im.vector.app.core.platform.VectorViewModel import kotlinx.coroutines.launch import org.matrix.android.sdk.api.session.Session +import org.matrix.android.sdk.api.session.getRoom import org.matrix.android.sdk.flow.flow import org.matrix.android.sdk.flow.unwrap @@ -36,7 +37,7 @@ class RoomNotificationSettingsViewModel @AssistedInject constructor( @AssistedFactory interface Factory : MavericksAssistedViewModelFactory { - override fun create(initialState: RoomNotificationSettingsViewState): RoomNotificationSettingsViewModel + override fun create(initialState: RoomNotificationSettingsViewState): RoomNotificationSettingsViewModel } companion object : MavericksViewModelFactory by hiltMavericksViewModelFactory() @@ -73,7 +74,7 @@ class RoomNotificationSettingsViewModel @AssistedInject constructor( private fun handleSelectNotificationState(action: RoomNotificationSettingsAction.SelectNotificationState) { setState { copy(isLoading = true) } viewModelScope.launch { - runCatching { room.setRoomNotificationState(action.notificationState) } + runCatching { room.setRoomNotificationState(action.notificationState) } .fold( { setState { diff --git a/vector/src/main/java/im/vector/app/features/roomprofile/notifications/RoomNotificationSettingsViewState.kt b/vector/src/main/java/im/vector/app/features/roomprofile/notifications/RoomNotificationSettingsViewState.kt index d6d1176c36..5c1e9e6bc5 100644 --- a/vector/src/main/java/im/vector/app/features/roomprofile/notifications/RoomNotificationSettingsViewState.kt +++ b/vector/src/main/java/im/vector/app/features/roomprofile/notifications/RoomNotificationSettingsViewState.kt @@ -30,7 +30,7 @@ data class RoomNotificationSettingsViewState( val roomSummary: Async = Uninitialized, val isLoading: Boolean = false, val notificationState: Async = Uninitialized -) : MavericksState { +) : MavericksState { constructor(args: RoomProfileArgs) : this(roomId = args.roomId) constructor(args: RoomListActionsArgs) : this(roomId = args.roomId) } diff --git a/vector/src/main/java/im/vector/app/features/roomprofile/permissions/RoomPermissionsFragment.kt b/vector/src/main/java/im/vector/app/features/roomprofile/permissions/RoomPermissionsFragment.kt index 0d5ac7dea8..49efc416ea 100644 --- a/vector/src/main/java/im/vector/app/features/roomprofile/permissions/RoomPermissionsFragment.kt +++ b/vector/src/main/java/im/vector/app/features/roomprofile/permissions/RoomPermissionsFragment.kt @@ -27,10 +27,10 @@ import com.airbnb.mvrx.withState import im.vector.app.R import im.vector.app.core.extensions.cleanup import im.vector.app.core.extensions.configureWith -import im.vector.app.core.extensions.exhaustive import im.vector.app.core.platform.VectorBaseFragment import im.vector.app.core.utils.toast import im.vector.app.databinding.FragmentRoomSettingGenericBinding +import im.vector.app.features.analytics.plan.MobileScreen import im.vector.app.features.home.AvatarRenderer import im.vector.app.features.roommemberprofile.powerlevel.EditPowerLevelDialogs import im.vector.app.features.roomprofile.RoomProfileArgs @@ -53,6 +53,11 @@ class RoomPermissionsFragment @Inject constructor( return FragmentRoomSettingGenericBinding.inflate(inflater, container, false) } + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + analyticsScreenName = MobileScreen.ScreenName.RoomPermissions + } + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) @@ -67,7 +72,7 @@ class RoomPermissionsFragment @Inject constructor( when (it) { is RoomPermissionsViewEvents.Failure -> showFailure(it.throwable) RoomPermissionsViewEvents.Success -> showSuccess() - }.exhaustive + } } } diff --git a/vector/src/main/java/im/vector/app/features/roomprofile/permissions/RoomPermissionsViewModel.kt b/vector/src/main/java/im/vector/app/features/roomprofile/permissions/RoomPermissionsViewModel.kt index 7e8a66d12a..e132e89d31 100644 --- a/vector/src/main/java/im/vector/app/features/roomprofile/permissions/RoomPermissionsViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/roomprofile/permissions/RoomPermissionsViewModel.kt @@ -23,7 +23,6 @@ import dagger.assisted.AssistedFactory import dagger.assisted.AssistedInject import im.vector.app.core.di.MavericksAssistedViewModelFactory import im.vector.app.core.di.hiltMavericksViewModelFactory -import im.vector.app.core.extensions.exhaustive import im.vector.app.core.platform.VectorViewModel import im.vector.app.features.powerlevel.PowerLevelsFlowFactory import kotlinx.coroutines.flow.launchIn @@ -32,6 +31,7 @@ import kotlinx.coroutines.launch import org.matrix.android.sdk.api.session.Session import org.matrix.android.sdk.api.session.events.model.EventType import org.matrix.android.sdk.api.session.events.model.toContent +import org.matrix.android.sdk.api.session.getRoom import org.matrix.android.sdk.api.session.room.model.PowerLevelsContent import org.matrix.android.sdk.api.session.room.powerlevels.PowerLevelsHelper import org.matrix.android.sdk.flow.flow @@ -39,7 +39,7 @@ import org.matrix.android.sdk.flow.unwrap class RoomPermissionsViewModel @AssistedInject constructor(@Assisted initialState: RoomPermissionsViewState, private val session: Session) : - VectorViewModel(initialState) { + VectorViewModel(initialState) { @AssistedFactory interface Factory : MavericksAssistedViewModelFactory { @@ -90,7 +90,7 @@ class RoomPermissionsViewModel @AssistedInject constructor(@Assisted initialStat when (action) { is RoomPermissionsAction.UpdatePermission -> updatePermission(action) RoomPermissionsAction.ToggleShowAllPermissions -> toggleShowAllPermissions() - }.exhaustive + } } private fun toggleShowAllPermissions() { diff --git a/vector/src/main/java/im/vector/app/features/roomprofile/settings/RoomSettingsFragment.kt b/vector/src/main/java/im/vector/app/features/roomprofile/settings/RoomSettingsFragment.kt index 51f6b247d4..eb69e36ba0 100644 --- a/vector/src/main/java/im/vector/app/features/roomprofile/settings/RoomSettingsFragment.kt +++ b/vector/src/main/java/im/vector/app/features/roomprofile/settings/RoomSettingsFragment.kt @@ -33,13 +33,13 @@ import im.vector.app.R import im.vector.app.core.dialogs.GalleryOrCameraDialogHelper import im.vector.app.core.extensions.cleanup import im.vector.app.core.extensions.configureWith -import im.vector.app.core.extensions.exhaustive import im.vector.app.core.intent.getFilenameFromUri import im.vector.app.core.platform.OnBackPressed import im.vector.app.core.platform.VectorBaseFragment import im.vector.app.core.resources.ColorProvider import im.vector.app.core.utils.toast import im.vector.app.databinding.FragmentRoomSettingGenericBinding +import im.vector.app.features.analytics.plan.MobileScreen import im.vector.app.features.home.AvatarRenderer import im.vector.app.features.roomprofile.RoomProfileArgs import im.vector.app.features.roomprofile.RoomProfileSharedActionViewModel @@ -78,6 +78,11 @@ class RoomSettingsFragment @Inject constructor( override fun getMenuRes() = R.menu.vector_room_settings + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + analyticsScreenName = MobileScreen.ScreenName.RoomSettings + } + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) roomProfileSharedActionViewModel = activityViewModelProvider.get(RoomProfileSharedActionViewModel::class.java) @@ -98,7 +103,7 @@ class RoomSettingsFragment @Inject constructor( ignoreChanges = true vectorBaseActivity.onBackPressed() } - }.exhaustive + } } } diff --git a/vector/src/main/java/im/vector/app/features/roomprofile/settings/RoomSettingsViewModel.kt b/vector/src/main/java/im/vector/app/features/roomprofile/settings/RoomSettingsViewModel.kt index a0325cfc2b..1041354fd5 100644 --- a/vector/src/main/java/im/vector/app/features/roomprofile/settings/RoomSettingsViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/roomprofile/settings/RoomSettingsViewModel.kt @@ -23,7 +23,6 @@ import dagger.assisted.AssistedFactory import dagger.assisted.AssistedInject import im.vector.app.core.di.MavericksAssistedViewModelFactory import im.vector.app.core.di.hiltMavericksViewModelFactory -import im.vector.app.core.extensions.exhaustive import im.vector.app.core.platform.VectorViewModel import im.vector.app.features.powerlevel.PowerLevelsFlowFactory import im.vector.app.features.settings.VectorPreferences @@ -36,6 +35,7 @@ import org.matrix.android.sdk.api.query.QueryStringValue import org.matrix.android.sdk.api.session.Session import org.matrix.android.sdk.api.session.events.model.EventType import org.matrix.android.sdk.api.session.events.model.toModel +import org.matrix.android.sdk.api.session.getRoom import org.matrix.android.sdk.api.session.homeserver.HomeServerCapabilities import org.matrix.android.sdk.api.session.room.model.RoomAvatarContent import org.matrix.android.sdk.api.session.room.model.RoomGuestAccessContent @@ -68,7 +68,7 @@ class RoomSettingsViewModel @AssistedInject constructor(@Assisted initialState: observeRoomAvatar() observeState() - val homeServerCapabilities = session.getHomeServerCapabilities() + val homeServerCapabilities = session.homeServerCapabilitiesService().getHomeServerCapabilities() val canUseRestricted = homeServerCapabilities .isFeatureSupported(HomeServerCapabilities.ROOM_CAP_RESTRICTED, room.getRoomVersion()) @@ -201,7 +201,7 @@ class RoomSettingsViewModel @AssistedInject constructor(@Assisted initialState: is RoomSettingsAction.SetRoomGuestAccess -> handleSetGuestAccess(action) is RoomSettingsAction.Save -> saveSettings() is RoomSettingsAction.Cancel -> cancel() - }.exhaustive + } } private fun handleSetRoomJoinRule(action: RoomSettingsAction.SetRoomJoinRule) = withState { state -> diff --git a/vector/src/main/java/im/vector/app/features/roomprofile/settings/RoomSettingsViewState.kt b/vector/src/main/java/im/vector/app/features/roomprofile/settings/RoomSettingsViewState.kt index 122e0034c6..fe7c984cb4 100644 --- a/vector/src/main/java/im/vector/app/features/roomprofile/settings/RoomSettingsViewState.kt +++ b/vector/src/main/java/im/vector/app/features/roomprofile/settings/RoomSettingsViewState.kt @@ -75,13 +75,13 @@ data class RoomSettingsViewState( fun getJoinRuleWording(stringProvider: StringProvider): String { return when (val joinRule = newRoomJoinRules.newJoinRules ?: currentRoomJoinRules) { - RoomJoinRules.INVITE -> { + RoomJoinRules.INVITE -> { stringProvider.getString(R.string.room_settings_room_access_private_title) } - RoomJoinRules.PUBLIC -> { + RoomJoinRules.PUBLIC -> { stringProvider.getString(R.string.room_settings_room_access_public_title) } - RoomJoinRules.KNOCK -> { + RoomJoinRules.KNOCK -> { stringProvider.getString(R.string.room_settings_room_access_entry_knock) } RoomJoinRules.RESTRICTED -> { diff --git a/vector/src/main/java/im/vector/app/features/roomprofile/settings/historyvisibility/RoomHistoryVisibilitySharedActionViewModel.kt b/vector/src/main/java/im/vector/app/features/roomprofile/settings/historyvisibility/RoomHistoryVisibilitySharedActionViewModel.kt index c4f4892984..2f1fc086db 100644 --- a/vector/src/main/java/im/vector/app/features/roomprofile/settings/historyvisibility/RoomHistoryVisibilitySharedActionViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/roomprofile/settings/historyvisibility/RoomHistoryVisibilitySharedActionViewModel.kt @@ -20,4 +20,4 @@ import im.vector.app.core.platform.VectorSharedActionViewModel import javax.inject.Inject class RoomHistoryVisibilitySharedActionViewModel @Inject constructor() : - VectorSharedActionViewModel() + VectorSharedActionViewModel() diff --git a/vector/src/main/java/im/vector/app/features/roomprofile/settings/historyvisibility/RoomHistoryVisibilityViewModel.kt b/vector/src/main/java/im/vector/app/features/roomprofile/settings/historyvisibility/RoomHistoryVisibilityViewModel.kt index 5580156918..e99a8d8a0b 100644 --- a/vector/src/main/java/im/vector/app/features/roomprofile/settings/historyvisibility/RoomHistoryVisibilityViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/roomprofile/settings/historyvisibility/RoomHistoryVisibilityViewModel.kt @@ -19,4 +19,4 @@ package im.vector.app.features.roomprofile.settings.historyvisibility import im.vector.app.core.ui.bottomsheet.BottomSheetGenericViewModel class RoomHistoryVisibilityViewModel(initialState: RoomHistoryVisibilityState) : - BottomSheetGenericViewModel(initialState) + BottomSheetGenericViewModel(initialState) diff --git a/vector/src/main/java/im/vector/app/features/roomprofile/settings/joinrule/RoomJoinRuleSharedActionViewModel.kt b/vector/src/main/java/im/vector/app/features/roomprofile/settings/joinrule/RoomJoinRuleSharedActionViewModel.kt index 971d71e0df..fd1fb4d61e 100644 --- a/vector/src/main/java/im/vector/app/features/roomprofile/settings/joinrule/RoomJoinRuleSharedActionViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/roomprofile/settings/joinrule/RoomJoinRuleSharedActionViewModel.kt @@ -20,4 +20,4 @@ import im.vector.app.core.platform.VectorSharedActionViewModel import javax.inject.Inject class RoomJoinRuleSharedActionViewModel @Inject constructor() : - VectorSharedActionViewModel() + VectorSharedActionViewModel() diff --git a/vector/src/main/java/im/vector/app/features/roomprofile/settings/joinrule/RoomJoinRuleViewModel.kt b/vector/src/main/java/im/vector/app/features/roomprofile/settings/joinrule/RoomJoinRuleViewModel.kt index 1ff374bf5b..e528c41676 100644 --- a/vector/src/main/java/im/vector/app/features/roomprofile/settings/joinrule/RoomJoinRuleViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/roomprofile/settings/joinrule/RoomJoinRuleViewModel.kt @@ -19,4 +19,4 @@ package im.vector.app.features.roomprofile.settings.joinrule import im.vector.app.core.ui.bottomsheet.BottomSheetGenericViewModel class RoomJoinRuleViewModel(initialState: RoomJoinRuleState) : - BottomSheetGenericViewModel(initialState) + BottomSheetGenericViewModel(initialState) diff --git a/vector/src/main/java/im/vector/app/features/roomprofile/settings/joinrule/advanced/RoomJoinRuleChooseRestrictedViewModel.kt b/vector/src/main/java/im/vector/app/features/roomprofile/settings/joinrule/advanced/RoomJoinRuleChooseRestrictedViewModel.kt index 548ec9cfe4..1707439f01 100644 --- a/vector/src/main/java/im/vector/app/features/roomprofile/settings/joinrule/advanced/RoomJoinRuleChooseRestrictedViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/roomprofile/settings/joinrule/advanced/RoomJoinRuleChooseRestrictedViewModel.kt @@ -29,7 +29,6 @@ import dagger.assisted.AssistedInject import im.vector.app.R import im.vector.app.core.di.MavericksAssistedViewModelFactory import im.vector.app.core.di.hiltMavericksViewModelFactory -import im.vector.app.core.extensions.exhaustive import im.vector.app.core.platform.VectorViewModel import im.vector.app.core.resources.StringProvider import im.vector.app.core.utils.styleMatchingText @@ -41,6 +40,8 @@ import org.matrix.android.sdk.api.query.QueryStringValue import org.matrix.android.sdk.api.session.Session import org.matrix.android.sdk.api.session.events.model.EventType import org.matrix.android.sdk.api.session.events.model.toModel +import org.matrix.android.sdk.api.session.getRoom +import org.matrix.android.sdk.api.session.getRoomSummary import org.matrix.android.sdk.api.session.homeserver.HomeServerCapabilities import org.matrix.android.sdk.api.session.room.model.Membership import org.matrix.android.sdk.api.session.room.model.RoomJoinRules @@ -98,7 +99,7 @@ class RoomJoinRuleChooseRestrictedViewModel @AssistedInject constructor( } } - val homeServerCapabilities = session.getHomeServerCapabilities() + val homeServerCapabilities = session.homeServerCapabilitiesService().getHomeServerCapabilities() var safeRule: RoomJoinRules = joinRulesContent?.joinRules ?: RoomJoinRules.INVITE // server is not really checking that, just to be sure let's check val restrictedSupportedByThisVersion = homeServerCapabilities @@ -180,7 +181,7 @@ class RoomJoinRuleChooseRestrictedViewModel @AssistedInject constructor( is RoomJoinRuleChooseRestrictedActions.SelectJoinRules -> handleSelectRule(action) is RoomJoinRuleChooseRestrictedActions.SwitchToRoomAfterMigration -> handleSwitchToRoom(action) RoomJoinRuleChooseRestrictedActions.DoUpdateJoinRules -> handleSubmit() - }.exhaustive + } checkForChanges() } @@ -356,7 +357,7 @@ class RoomJoinRuleChooseRestrictedViewModel @AssistedInject constructor( viewModelScope.launch { if (vectorPreferences.developerMode()) { // in developer mode we let you choose any room or space to restrict to - val filteredCandidates = session.getRoomSummaries( + val filteredCandidates = session.roomService().getRoomSummaries( roomSummaryQueryParams { excludeType = null displayName = QueryStringValue.Contains(action.filter, QueryStringValue.Case.INSENSITIVE) diff --git a/vector/src/main/java/im/vector/app/features/roomprofile/uploads/RoomUploadsFragment.kt b/vector/src/main/java/im/vector/app/features/roomprofile/uploads/RoomUploadsFragment.kt index a0adf42d5b..6a115ad272 100644 --- a/vector/src/main/java/im/vector/app/features/roomprofile/uploads/RoomUploadsFragment.kt +++ b/vector/src/main/java/im/vector/app/features/roomprofile/uploads/RoomUploadsFragment.kt @@ -28,7 +28,6 @@ import com.airbnb.mvrx.withState import com.google.android.material.appbar.AppBarLayout import com.google.android.material.tabs.TabLayoutMediator import im.vector.app.R -import im.vector.app.core.extensions.exhaustive import im.vector.app.core.intent.getMimeTypeFromUri import im.vector.app.core.platform.VectorBaseFragment import im.vector.app.core.utils.saveMedia @@ -99,7 +98,7 @@ class RoomUploadsFragment @Inject constructor( Unit } is RoomUploadsViewEvents.Failure -> showFailure(it.throwable) - }.exhaustive + } } } diff --git a/vector/src/main/java/im/vector/app/features/roomprofile/uploads/RoomUploadsViewModel.kt b/vector/src/main/java/im/vector/app/features/roomprofile/uploads/RoomUploadsViewModel.kt index 92ff33395e..0fbcab9327 100644 --- a/vector/src/main/java/im/vector/app/features/roomprofile/uploads/RoomUploadsViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/roomprofile/uploads/RoomUploadsViewModel.kt @@ -25,10 +25,10 @@ import dagger.assisted.AssistedFactory import dagger.assisted.AssistedInject import im.vector.app.core.di.MavericksAssistedViewModelFactory import im.vector.app.core.di.hiltMavericksViewModelFactory -import im.vector.app.core.extensions.exhaustive import im.vector.app.core.platform.VectorViewModel import kotlinx.coroutines.launch import org.matrix.android.sdk.api.session.Session +import org.matrix.android.sdk.api.session.getRoom import org.matrix.android.sdk.api.session.room.model.message.MessageType import org.matrix.android.sdk.flow.flow import org.matrix.android.sdk.flow.unwrap @@ -110,7 +110,7 @@ class RoomUploadsViewModel @AssistedInject constructor( is RoomUploadsAction.Share -> handleShare(action) RoomUploadsAction.Retry -> handleLoadMore() RoomUploadsAction.LoadMore -> handleLoadMore() - }.exhaustive + } } private fun handleShare(action: RoomUploadsAction.Share) { diff --git a/vector/src/main/java/im/vector/app/features/roomprofile/uploads/files/RoomUploadsFilesFragment.kt b/vector/src/main/java/im/vector/app/features/roomprofile/uploads/files/RoomUploadsFilesFragment.kt index 1739378761..4b5d44e886 100644 --- a/vector/src/main/java/im/vector/app/features/roomprofile/uploads/files/RoomUploadsFilesFragment.kt +++ b/vector/src/main/java/im/vector/app/features/roomprofile/uploads/files/RoomUploadsFilesFragment.kt @@ -108,6 +108,7 @@ class RoomUploadsFilesFragment @Inject constructor( ) } } + else -> Unit } } else { views.genericStateViewListStateView.state = StateView.State.Content diff --git a/vector/src/main/java/im/vector/app/features/roomprofile/uploads/media/RoomUploadsMediaFragment.kt b/vector/src/main/java/im/vector/app/features/roomprofile/uploads/media/RoomUploadsMediaFragment.kt index eb4337cffa..99701f08c3 100644 --- a/vector/src/main/java/im/vector/app/features/roomprofile/uploads/media/RoomUploadsMediaFragment.kt +++ b/vector/src/main/java/im/vector/app/features/roomprofile/uploads/media/RoomUploadsMediaFragment.kt @@ -46,11 +46,11 @@ import im.vector.app.features.roomprofile.uploads.RoomUploadsAction import im.vector.app.features.roomprofile.uploads.RoomUploadsFragment import im.vector.app.features.roomprofile.uploads.RoomUploadsViewModel import im.vector.app.features.roomprofile.uploads.RoomUploadsViewState +import org.matrix.android.sdk.api.session.crypto.attachments.toElementToDecrypt import org.matrix.android.sdk.api.session.room.model.message.MessageImageContent import org.matrix.android.sdk.api.session.room.model.message.MessageVideoContent import org.matrix.android.sdk.api.session.room.model.message.getFileUrl import org.matrix.android.sdk.api.session.room.model.message.getThumbnailUrl -import org.matrix.android.sdk.internal.crypto.attachments.toElementToDecrypt import javax.inject.Inject class RoomUploadsMediaFragment @Inject constructor( @@ -205,6 +205,7 @@ class RoomUploadsMediaFragment @Inject constructor( ) } } + else -> Unit } } else { views.genericStateViewListStateView.state = StateView.State.Content diff --git a/vector/src/main/java/im/vector/app/features/roomprofile/uploads/media/UploadsMediaController.kt b/vector/src/main/java/im/vector/app/features/roomprofile/uploads/media/UploadsMediaController.kt index 0a5f498b9d..94d29b3078 100644 --- a/vector/src/main/java/im/vector/app/features/roomprofile/uploads/media/UploadsMediaController.kt +++ b/vector/src/main/java/im/vector/app/features/roomprofile/uploads/media/UploadsMediaController.kt @@ -26,13 +26,13 @@ import im.vector.app.core.utils.DimensionConverter import im.vector.app.features.media.ImageContentRenderer import im.vector.app.features.media.VideoContentRenderer import im.vector.app.features.roomprofile.uploads.RoomUploadsViewState +import org.matrix.android.sdk.api.session.crypto.attachments.toElementToDecrypt import org.matrix.android.sdk.api.session.room.model.message.MessageImageContent import org.matrix.android.sdk.api.session.room.model.message.MessageType import org.matrix.android.sdk.api.session.room.model.message.MessageVideoContent import org.matrix.android.sdk.api.session.room.model.message.getFileUrl import org.matrix.android.sdk.api.session.room.model.message.getThumbnailUrl import org.matrix.android.sdk.api.session.room.uploads.UploadEvent -import org.matrix.android.sdk.internal.crypto.attachments.toElementToDecrypt import javax.inject.Inject class UploadsMediaController @Inject constructor( diff --git a/vector/src/main/java/im/vector/app/features/session/SessionScopedProperty.kt b/vector/src/main/java/im/vector/app/features/session/SessionScopedProperty.kt index e6a84a41d8..64d49800c8 100644 --- a/vector/src/main/java/im/vector/app/features/session/SessionScopedProperty.kt +++ b/vector/src/main/java/im/vector/app/features/session/SessionScopedProperty.kt @@ -23,9 +23,9 @@ import kotlin.reflect.KProperty * This is a simple hack for having some Session scope dependencies. * Probably a temporary solution waiting for refactoring the Dagger management of Session. * You should use it with an extension property : - val Session.myProperty: MyProperty by SessionScopedProperty { - init code - } +val Session.myProperty: MyProperty by SessionScopedProperty { +init code +} * */ class SessionScopedProperty(val initializer: (Session) -> T) { diff --git a/vector/src/main/java/im/vector/app/features/session/VectorSessionStore.kt b/vector/src/main/java/im/vector/app/features/session/VectorSessionStore.kt index a2f3196979..79183e1808 100644 --- a/vector/src/main/java/im/vector/app/features/session/VectorSessionStore.kt +++ b/vector/src/main/java/im/vector/app/features/session/VectorSessionStore.kt @@ -22,7 +22,7 @@ import androidx.datastore.preferences.core.stringPreferencesKey import im.vector.app.core.extensions.dataStoreProvider import im.vector.app.features.onboarding.FtueUseCase import kotlinx.coroutines.flow.first -import org.matrix.android.sdk.internal.util.md5 +import org.matrix.android.sdk.api.util.md5 /** * User session scoped storage for: diff --git a/vector/src/main/java/im/vector/app/features/settings/SecretsSynchronisationInfo.kt b/vector/src/main/java/im/vector/app/features/settings/SecretsSynchronisationInfo.kt index e21366db02..d667a4f589 100644 --- a/vector/src/main/java/im/vector/app/features/settings/SecretsSynchronisationInfo.kt +++ b/vector/src/main/java/im/vector/app/features/settings/SecretsSynchronisationInfo.kt @@ -45,7 +45,7 @@ fun Session.liveSecretSynchronisationInfo(): Flow { sessionFlow.liveCrossSigningPrivateKeys() ) { _, crossSigningInfo, pInfo -> // first check if 4S is already setup - val is4SSetup = sharedSecretStorageService.isRecoverySetup() + val is4SSetup = sharedSecretStorageService().isRecoverySetup() val isCrossSigningEnabled = crossSigningInfo.getOrNull() != null val isCrossSigningTrusted = crossSigningInfo.getOrNull()?.isTrusted() == true val allPrivateKeysKnown = pInfo.getOrNull()?.allKnown().orFalse() @@ -63,7 +63,7 @@ fun Session.liveSecretSynchronisationInfo(): Flow { allPrivateKeysKnown = allPrivateKeysKnown, megolmBackupAvailable = megolmBackupAvailable, megolmSecretKnown = megolmKeyKnown, - isMegolmKeyIn4S = sharedSecretStorageService.isMegolmKeyInBackup() + isMegolmKeyIn4S = sharedSecretStorageService().isMegolmKeyInBackup() ) } .distinctUntilChanged() diff --git a/vector/src/main/java/im/vector/app/features/settings/VectorPreferences.kt b/vector/src/main/java/im/vector/app/features/settings/VectorPreferences.kt index 352c5768fb..195072465b 100755 --- a/vector/src/main/java/im/vector/app/features/settings/VectorPreferences.kt +++ b/vector/src/main/java/im/vector/app/features/settings/VectorPreferences.kt @@ -100,6 +100,7 @@ class VectorPreferences @Inject constructor(private val context: Context) { private const val SETTINGS_ENABLE_CHAT_EFFECTS = "SETTINGS_ENABLE_CHAT_EFFECTS" private const val SETTINGS_SHOW_EMOJI_KEYBOARD = "SETTINGS_SHOW_EMOJI_KEYBOARD" private const val SETTINGS_LABS_ENABLE_LATEX_MATHS = "SETTINGS_LABS_ENABLE_LATEX_MATHS" + const val SETTINGS_PRESENCE_USER_ALWAYS_APPEARS_OFFLINE = "SETTINGS_PRESENCE_USER_ALWAYS_APPEARS_OFFLINE" // Room directory private const val SETTINGS_ROOM_DIRECTORY_SHOW_ALL_PUBLIC_ROOMS = "SETTINGS_ROOM_DIRECTORY_SHOW_ALL_PUBLIC_ROOMS" @@ -111,9 +112,6 @@ class VectorPreferences @Inject constructor(private val context: Context) { private const val SETTINGS_PIN_UNREAD_MESSAGES_PREFERENCE_KEY = "SETTINGS_PIN_UNREAD_MESSAGES_PREFERENCE_KEY" private const val SETTINGS_PIN_MISSED_NOTIFICATIONS_PREFERENCE_KEY = "SETTINGS_PIN_MISSED_NOTIFICATIONS_PREFERENCE_KEY" - // flair - const val SETTINGS_GROUPS_FLAIR_KEY = "SETTINGS_GROUPS_FLAIR_KEY" - // notifications const val SETTINGS_ENABLE_ALL_NOTIF_PREFERENCE_KEY = "SETTINGS_ENABLE_ALL_NOTIF_PREFERENCE_KEY" const val SETTINGS_ENABLE_THIS_DEVICE_PREFERENCE_KEY = "SETTINGS_ENABLE_THIS_DEVICE_PREFERENCE_KEY" @@ -201,7 +199,13 @@ class VectorPreferences @Inject constructor(private val context: Context) { private const val TAKE_PHOTO_VIDEO_MODE = "TAKE_PHOTO_VIDEO_MODE" private const val SETTINGS_LABS_RENDER_LOCATIONS_IN_TIMELINE = "SETTINGS_LABS_RENDER_LOCATIONS_IN_TIMELINE" - const val SETTINGS_LABS_ENABLE_THREAD_MESSAGES = "SETTINGS_LABS_ENABLE_THREAD_MESSAGES" + + // This key will be used to identify clients with the old thread support enabled io.element.thread + const val SETTINGS_LABS_ENABLE_THREAD_MESSAGES_OLD_CLIENTS = "SETTINGS_LABS_ENABLE_THREAD_MESSAGES" + + // This key will be used to identify clients with the new thread support enabled m.thread + const val SETTINGS_LABS_ENABLE_THREAD_MESSAGES = "SETTINGS_LABS_ENABLE_THREAD_MESSAGES_FINAL" + const val SETTINGS_THREAD_MESSAGES_SYNCED = "SETTINGS_THREAD_MESSAGES_SYNCED" // Possible values for TAKE_PHOTO_VIDEO_MODE const val TAKE_PHOTO_VIDEO_MODE_ALWAYS_ASK = 0 @@ -862,6 +866,27 @@ class VectorPreferences @Inject constructor(private val context: Context) { return defaultPrefs.getBoolean(SETTINGS_INTERFACE_BUBBLE_KEY, getDefault(R.bool.settings_interface_bubble_default)) } + /** + * Tells if user should always appear offline or not. + * + * @return true if user should always appear offline + */ + fun userAlwaysAppearsOffline(): Boolean { + return defaultPrefs.getBoolean(SETTINGS_PRESENCE_USER_ALWAYS_APPEARS_OFFLINE, + getDefault(R.bool.settings_presence_user_always_appears_offline_default)) + } + + /** + * Update the rage shake enabled status. + * + * @param isEnabled true to enable rage shake. + */ + fun setRageshakeEnabled(isEnabled: Boolean) { + defaultPrefs.edit { + putBoolean(SETTINGS_USE_RAGE_SHAKE_KEY, isEnabled) + } + } + /** * Tells if the rage shake is used. * @@ -1006,7 +1031,56 @@ class VectorPreferences @Inject constructor(private val context: Context) { return defaultPrefs.getBoolean(SETTINGS_LABS_RENDER_LOCATIONS_IN_TIMELINE, true) } + /** + * Indicates whether or not thread messages are enabled + */ fun areThreadMessagesEnabled(): Boolean { - return defaultPrefs.getBoolean(SETTINGS_LABS_ENABLE_THREAD_MESSAGES, false) + return defaultPrefs.getBoolean(SETTINGS_LABS_ENABLE_THREAD_MESSAGES, getDefault(R.bool.settings_labs_thread_messages_default)) + } + + /** + * Manually sets thread messages enabled, useful for migrating users from io.element.thread + */ + fun setThreadMessagesEnabled() { + defaultPrefs + .edit() + .putBoolean(SETTINGS_LABS_ENABLE_THREAD_MESSAGES, true) + .apply() + } + + /** + * Indicates whether or not the user will be notified about the new thread support + * We should notify the user only if he had old thread support enabled + */ + fun shouldNotifyUserAboutThreads(): Boolean { + return defaultPrefs.getBoolean(SETTINGS_LABS_ENABLE_THREAD_MESSAGES_OLD_CLIENTS, false) + } + + /** + * Indicates that the user have been notified about threads migration + */ + fun userNotifiedAboutThreads() { + defaultPrefs + .edit() + .putBoolean(SETTINGS_LABS_ENABLE_THREAD_MESSAGES_OLD_CLIENTS, false) + .apply() + } + + /** + * Indicates whether or not we should clear cache for threads migration. + * Default value is true, for fresh installs and updates + */ + fun shouldMigrateThreads(): Boolean { + return defaultPrefs.getBoolean(SETTINGS_THREAD_MESSAGES_SYNCED, true) + } + + /** + * Indicates that there no longer threads migration needed + */ + fun setShouldMigrateThreads(shouldMigrate: Boolean) { + defaultPrefs + .edit() + .putBoolean(SETTINGS_THREAD_MESSAGES_SYNCED, shouldMigrate) + .apply() } } diff --git a/vector/src/main/java/im/vector/app/features/settings/VectorSettingsAdvancedSettingsFragment.kt b/vector/src/main/java/im/vector/app/features/settings/VectorSettingsAdvancedSettingsFragment.kt index 68ce4e691c..b616ce1e1f 100644 --- a/vector/src/main/java/im/vector/app/features/settings/VectorSettingsAdvancedSettingsFragment.kt +++ b/vector/src/main/java/im/vector/app/features/settings/VectorSettingsAdvancedSettingsFragment.kt @@ -16,12 +16,14 @@ package im.vector.app.features.settings +import android.os.Bundle import androidx.preference.Preference import androidx.preference.SeekBarPreference import im.vector.app.R import im.vector.app.core.platform.VectorBaseActivity import im.vector.app.core.preference.VectorPreferenceCategory import im.vector.app.core.preference.VectorSwitchPreference +import im.vector.app.features.analytics.plan.MobileScreen import im.vector.app.features.rageshake.RageShake class VectorSettingsAdvancedSettingsFragment : VectorSettingsBaseFragment() { @@ -31,6 +33,11 @@ class VectorSettingsAdvancedSettingsFragment : VectorSettingsBaseFragment() { private var rageshake: RageShake? = null + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + analyticsScreenName = MobileScreen.ScreenName.SettingsAdvanced + } + override fun onResume() { super.onResume() diff --git a/vector/src/main/java/im/vector/app/features/settings/VectorSettingsFlairFragment.kt b/vector/src/main/java/im/vector/app/features/settings/VectorSettingsFlairFragment.kt deleted file mode 100644 index ec65e7d004..0000000000 --- a/vector/src/main/java/im/vector/app/features/settings/VectorSettingsFlairFragment.kt +++ /dev/null @@ -1,170 +0,0 @@ -/* - * Copyright 2019 New Vector Ltd - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package im.vector.app.features.settings - -import androidx.preference.PreferenceCategory -import im.vector.app.R -import im.vector.app.core.preference.ProgressBarPreference - -class VectorSettingsFlairFragment : VectorSettingsBaseFragment() { - - override var titleRes = R.string.settings_flair - override val preferenceXmlRes = R.xml.vector_settings_flair - - // current publicised group list - private var mPublicisedGroups: MutableSet? = null - - // Group Flairs - private val mGroupsFlairCategory by lazy { - findPreference(VectorPreferences.SETTINGS_GROUPS_FLAIR_KEY)!! - } - - override fun bindPref() { - // Flair - refreshGroupFlairsList() - } - - // ============================================================================================================== - // Group flairs management - // ============================================================================================================== - - /** - * Force the refresh of the devices list.

    - * The devices list is the list of the devices where the user as looged in. - * It can be any mobile device, as any browser. - */ - private fun refreshGroupFlairsList() { - // display a spinner while refreshing - if (0 == mGroupsFlairCategory.preferenceCount) { - activity?.let { - val preference = ProgressBarPreference(it) - mGroupsFlairCategory.addPreference(preference) - } - } - - /* - TODO - session.groupsManager.getUserPublicisedGroups(session.myUserId, true, object : MatrixCallback> { - override fun onSuccess(publicisedGroups: Set) { - // clear everything - mGroupsFlairCategory.removeAll() - - if (publicisedGroups.isEmpty()) { - val vectorGroupPreference = Preference(activity) - vectorGroupPreference.title = resources.getString(R.string.settings_without_flair) - mGroupsFlairCategory.addPreference(vectorGroupPreference) - } else { - buildGroupsList(publicisedGroups) - } - } - - override fun onNetworkError(e: Exception) { - // NOP - } - - override fun onMatrixError(e: MatrixError) { - // NOP - } - - override fun onUnexpectedError(e: Exception) { - // NOP - } - }) - */ - } - - /** - * Build the groups list. - * - * @param publicisedGroups the publicised groups list. - */ - private fun buildGroupsList(publicisedGroups: Set) { - var isNewList = true - - mPublicisedGroups?.let { - if (it.size == publicisedGroups.size) { - isNewList = !it.containsAll(publicisedGroups) - } - } - - if (isNewList) { - /* - TODO - val joinedGroups = ArrayList(session.groupsManager.joinedGroups) - Collections.sort(joinedGroups, Group.mGroupsComparator) - - mPublicisedGroups = publicisedGroups.toMutableSet() - - for ((prefIndex, group) in joinedGroups.withIndex()) { - val vectorGroupPreference = VectorGroupPreference(activity!!) - vectorGroupPreference.key = DEVICES_PREFERENCE_KEY_BASE + prefIndex - - vectorGroupPreference.setGroup(group, session) - vectorGroupPreference.title = group.displayName - vectorGroupPreference.summary = group.groupId - - vectorGroupPreference.isChecked = publicisedGroups.contains(group.groupId) - mGroupsFlairCategory.addPreference(vectorGroupPreference) - - vectorGroupPreference.onPreferenceChangeListener = Preference.OnPreferenceChangeListener { _, newValue -> - if (newValue is Boolean) { - /* - * if mPublicisedGroup is null somehow, then - * we cant check it contains groupId or not - * so set isFlaired to false - */ - val isFlaired = mPublicisedGroups?.contains(group.groupId) ?: false - - if (newValue != isFlaired) { - displayLoadingView() - session.groupsManager.updateGroupPublicity(group.groupId, newValue, object : MatrixCallback { - override fun onSuccess(info: Void?) { - hideLoadingView() - if (newValue) { - mPublicisedGroups?.add(group.groupId) - } else { - mPublicisedGroups?.remove(group.groupId) - } - } - - private fun onError() { - hideLoadingView() - // restore default value - vectorGroupPreference.isChecked = publicisedGroups.contains(group.groupId) - } - - override fun onNetworkError(e: Exception) { - onError() - } - - override fun onMatrixError(e: MatrixError) { - onError() - } - - override fun onUnexpectedError(e: Exception) { - onError() - } - }) - } - } - true - } - } - */ - } - } -} diff --git a/vector/src/main/java/im/vector/app/features/settings/VectorSettingsGeneralFragment.kt b/vector/src/main/java/im/vector/app/features/settings/VectorSettingsGeneralFragment.kt index ffb9fc4af4..678356b05b 100644 --- a/vector/src/main/java/im/vector/app/features/settings/VectorSettingsGeneralFragment.kt +++ b/vector/src/main/java/im/vector/app/features/settings/VectorSettingsGeneralFragment.kt @@ -51,6 +51,7 @@ import im.vector.app.core.utils.toast import im.vector.app.databinding.DialogChangePasswordBinding import im.vector.app.features.MainActivity import im.vector.app.features.MainActivityArgs +import im.vector.app.features.analytics.plan.MobileScreen import im.vector.app.features.discovery.DiscoverySettingsFragment import im.vector.app.features.navigation.SettingsActivityPayload import im.vector.app.features.workers.signout.SignOutUiWorker @@ -63,6 +64,7 @@ import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.launch import kotlinx.coroutines.withContext import org.matrix.android.sdk.api.failure.isInvalidPassword +import org.matrix.android.sdk.api.session.getUser import org.matrix.android.sdk.api.session.integrationmanager.IntegrationManagerConfig import org.matrix.android.sdk.api.session.integrationmanager.IntegrationManagerService import org.matrix.android.sdk.flow.flow @@ -117,6 +119,11 @@ class VectorSettingsGeneralFragment @Inject constructor( } } + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + analyticsScreenName = MobileScreen.ScreenName.SettingsGeneral + } + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) @@ -171,7 +178,7 @@ class VectorSettingsGeneralFragment @Inject constructor( // Password // Hide the preference if password can not be updated - if (session.getHomeServerCapabilities().canChangePassword) { + if (session.homeServerCapabilitiesService().getHomeServerCapabilities().canChangePassword) { mPasswordPreference.onPreferenceClickListener = Preference.OnPreferenceClickListener { onPasswordUpdateClick() false @@ -326,7 +333,7 @@ class VectorSettingsGeneralFragment @Inject constructor( lifecycleScope.launch { val result = runCatching { - session.updateAvatar(session.myUserId, uri, getFilenameFromUri(context, uri) ?: UUID.randomUUID().toString()) + session.profileService().updateAvatar(session.myUserId, uri, getFilenameFromUri(context, uri) ?: UUID.randomUUID().toString()) } if (!isAdded) return@launch @@ -359,7 +366,7 @@ class VectorSettingsGeneralFragment @Inject constructor( startActivityForResult(intent, REQUEST_PHONEBOOK_COUNTRY) true } - */ + */ } // ============================================================================================================== @@ -438,7 +445,7 @@ class VectorSettingsGeneralFragment @Inject constructor( showPasswordLoadingView(true) lifecycleScope.launch { val result = runCatching { - session.changePassword(oldPwd, newPwd) + session.accountService().changePassword(oldPwd, newPwd) } if (!isAdded) { return@launch @@ -470,7 +477,7 @@ class VectorSettingsGeneralFragment @Inject constructor( displayLoadingView() lifecycleScope.launch { - val result = runCatching { session.setDisplayName(session.myUserId, value) } + val result = runCatching { session.profileService().setDisplayName(session.myUserId, value) } if (!isAdded) return@launch result.fold( onSuccess = { diff --git a/vector/src/main/java/im/vector/app/features/settings/VectorSettingsHelpAboutFragment.kt b/vector/src/main/java/im/vector/app/features/settings/VectorSettingsHelpAboutFragment.kt index 31d9cf0426..3a999f299f 100644 --- a/vector/src/main/java/im/vector/app/features/settings/VectorSettingsHelpAboutFragment.kt +++ b/vector/src/main/java/im/vector/app/features/settings/VectorSettingsHelpAboutFragment.kt @@ -16,6 +16,7 @@ package im.vector.app.features.settings +import android.os.Bundle import androidx.preference.Preference import im.vector.app.BuildConfig import im.vector.app.R @@ -24,6 +25,7 @@ import im.vector.app.core.utils.FirstThrottler import im.vector.app.core.utils.copyToClipboard import im.vector.app.core.utils.openAppSettingsPage import im.vector.app.core.utils.openUrlInChromeCustomTab +import im.vector.app.features.analytics.plan.MobileScreen import im.vector.app.features.version.VersionProvider import org.matrix.android.sdk.api.Matrix import javax.inject.Inject @@ -37,6 +39,11 @@ class VectorSettingsHelpAboutFragment @Inject constructor( private val firstThrottler = FirstThrottler(1000) + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + analyticsScreenName = MobileScreen.ScreenName.SettingsHelp + } + override fun bindPref() { // Help findPreference(VectorPreferences.SETTINGS_HELP_PREFERENCE_KEY)!! diff --git a/vector/src/main/java/im/vector/app/features/settings/VectorSettingsLabsFragment.kt b/vector/src/main/java/im/vector/app/features/settings/VectorSettingsLabsFragment.kt index 118e820f84..e1e155865a 100644 --- a/vector/src/main/java/im/vector/app/features/settings/VectorSettingsLabsFragment.kt +++ b/vector/src/main/java/im/vector/app/features/settings/VectorSettingsLabsFragment.kt @@ -16,23 +16,34 @@ package im.vector.app.features.settings +import android.os.Bundle +import android.text.method.LinkMovementMethod +import android.widget.TextView import androidx.preference.Preference +import com.google.android.material.dialog.MaterialAlertDialogBuilder import im.vector.app.R import im.vector.app.core.preference.VectorSwitchPreference import im.vector.app.features.MainActivity import im.vector.app.features.MainActivityArgs -import org.matrix.android.sdk.internal.database.lightweight.LightweightSettingsStorage +import im.vector.app.features.analytics.plan.MobileScreen +import im.vector.app.features.home.room.threads.ThreadsManager +import org.matrix.android.sdk.api.settings.LightweightSettingsStorage import javax.inject.Inject class VectorSettingsLabsFragment @Inject constructor( private val vectorPreferences: VectorPreferences, - private val lightweightSettingsStorage: LightweightSettingsStorage - + private val lightweightSettingsStorage: LightweightSettingsStorage, + private val threadsManager: ThreadsManager ) : VectorSettingsBaseFragment() { override var titleRes = R.string.room_settings_labs_pref_title override val preferenceXmlRes = R.xml.vector_settings_labs + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + analyticsScreenName = MobileScreen.ScreenName.SettingsLabs + } + override fun bindPref() { findPreference(VectorPreferences.SETTINGS_LABS_AUTO_REPORT_UISI)?.let { pref -> // ensure correct default @@ -40,13 +51,51 @@ class VectorSettingsLabsFragment @Inject constructor( } // clear cache - findPreference(VectorPreferences.SETTINGS_LABS_ENABLE_THREAD_MESSAGES)?.let { - it.onPreferenceClickListener = Preference.OnPreferenceClickListener { - lightweightSettingsStorage.setThreadMessagesEnabled(vectorPreferences.areThreadMessagesEnabled()) - displayLoadingView() - MainActivity.restartApp(requireActivity(), MainActivityArgs(clearCache = true)) + findPreference(VectorPreferences.SETTINGS_LABS_ENABLE_THREAD_MESSAGES)?.let { vectorPref -> + vectorPref.onPreferenceClickListener = Preference.OnPreferenceClickListener { + onThreadsPreferenceClickedInterceptor(vectorPref) false } } } + + /** + * Intercept the click to display a user friendly dialog when their homeserver do not support threads + */ + private fun onThreadsPreferenceClickedInterceptor(vectorSwitchPreference: VectorSwitchPreference) { + val userEnabledThreads = vectorPreferences.areThreadMessagesEnabled() + if (!session.homeServerCapabilitiesService().getHomeServerCapabilities().canUseThreading && userEnabledThreads) { + activity?.let { + MaterialAlertDialogBuilder(it) + .setTitle(R.string.threads_labs_enable_notice_title) + .setMessage(threadsManager.getLabsEnableThreadsMessage()) + .setCancelable(true) + .setNegativeButton(R.string.action_not_now) { _, _ -> + vectorSwitchPreference.isChecked = false + } + .setPositiveButton(R.string.action_try_it_out) { _, _ -> + onThreadsPreferenceClicked() + } + .show() + ?.findViewById(android.R.id.message) + ?.apply { + linksClickable = true + movementMethod = LinkMovementMethod.getInstance() + } + } + } else { + onThreadsPreferenceClicked() + } + } + + /** + * Action when threads preference switch is actually clicked + */ + private fun onThreadsPreferenceClicked() { + // We should migrate threads only if threads are disabled + vectorPreferences.setShouldMigrateThreads(!vectorPreferences.areThreadMessagesEnabled()) + lightweightSettingsStorage.setThreadMessagesEnabled(vectorPreferences.areThreadMessagesEnabled()) + displayLoadingView() + MainActivity.restartApp(requireActivity(), MainActivityArgs(clearCache = true)) + } } diff --git a/vector/src/main/java/im/vector/app/features/settings/VectorSettingsPreferencesFragment.kt b/vector/src/main/java/im/vector/app/features/settings/VectorSettingsPreferencesFragment.kt index 50e32ae453..af9ac52d1e 100644 --- a/vector/src/main/java/im/vector/app/features/settings/VectorSettingsPreferencesFragment.kt +++ b/vector/src/main/java/im/vector/app/features/settings/VectorSettingsPreferencesFragment.kt @@ -18,8 +18,10 @@ package im.vector.app.features.settings import android.app.Activity import android.content.Context +import android.os.Bundle import android.widget.CheckedTextView import androidx.core.view.children +import androidx.lifecycle.lifecycleScope import androidx.preference.Preference import com.google.android.material.dialog.MaterialAlertDialogBuilder import im.vector.app.BuildConfig @@ -32,8 +34,11 @@ import im.vector.app.core.preference.VectorSwitchPreference import im.vector.app.databinding.DialogSelectTextSizeBinding import im.vector.app.features.MainActivity import im.vector.app.features.MainActivityArgs +import im.vector.app.features.analytics.plan.MobileScreen import im.vector.app.features.configuration.VectorConfiguration import im.vector.app.features.themes.ThemeUtils +import kotlinx.coroutines.launch +import org.matrix.android.sdk.api.session.presence.model.PresenceEnum import javax.inject.Inject class VectorSettingsPreferencesFragment @Inject constructor( @@ -54,6 +59,11 @@ class VectorSettingsPreferencesFragment @Inject constructor( findPreference("SETTINGS_INTERFACE_TAKE_PHOTO_VIDEO")!! } + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + analyticsScreenName = MobileScreen.ScreenName.SettingsPreferences + } + override fun bindPref() { // user interface preferences setUserInterfacePreferences() @@ -71,6 +81,17 @@ class VectorSettingsPreferencesFragment @Inject constructor( } } + findPreference(VectorPreferences.SETTINGS_PRESENCE_USER_ALWAYS_APPEARS_OFFLINE)!!.let { pref -> + pref.isChecked = vectorPreferences.userAlwaysAppearsOffline() + pref.setOnPreferenceChangeListener { _, newValue -> + val presenceOfflineModeEnabled = newValue as? Boolean ?: false + lifecycleScope.launch { + session.presenceService().setMyPresence(if (presenceOfflineModeEnabled) PresenceEnum.OFFLINE else PresenceEnum.ONLINE) + } + true + } + } + findPreference(VectorPreferences.SETTINGS_PREF_SPACE_SHOW_ALL_ROOM_IN_HOME)!!.let { pref -> pref.isChecked = vectorPreferences.prefSpacesShowAllRoomInHome() pref.setOnPreferenceChangeListener { _, _ -> @@ -117,7 +138,7 @@ class VectorSettingsPreferencesFragment @Inject constructor( false } } - */ + */ // update keep medias period findPreference(VectorPreferences.SETTINGS_MEDIA_SAVING_PERIOD_KEY)!!.let { diff --git a/vector/src/main/java/im/vector/app/features/settings/VectorSettingsSecurityPrivacyFragment.kt b/vector/src/main/java/im/vector/app/features/settings/VectorSettingsSecurityPrivacyFragment.kt index ef87d908ea..70ed3f441e 100644 --- a/vector/src/main/java/im/vector/app/features/settings/VectorSettingsSecurityPrivacyFragment.kt +++ b/vector/src/main/java/im/vector/app/features/settings/VectorSettingsSecurityPrivacyFragment.kt @@ -73,9 +73,9 @@ import me.gujun.android.span.span import org.matrix.android.sdk.api.MatrixCallback import org.matrix.android.sdk.api.extensions.getFingerprintHumanReadable import org.matrix.android.sdk.api.raw.RawService -import org.matrix.android.sdk.internal.crypto.crosssigning.isVerified -import org.matrix.android.sdk.internal.crypto.model.rest.DeviceInfo -import org.matrix.android.sdk.internal.crypto.model.rest.DevicesListResponse +import org.matrix.android.sdk.api.session.crypto.crosssigning.isVerified +import org.matrix.android.sdk.api.session.crypto.model.DeviceInfo +import org.matrix.android.sdk.api.session.crypto.model.DevicesListResponse import javax.inject.Inject class VectorSettingsSecurityPrivacyFragment @Inject constructor( @@ -186,6 +186,10 @@ class VectorSettingsSecurityPrivacyFragment @Inject constructor( private val secureBackupPreference by lazy { findPreference("SETTINGS_SECURE_BACKUP_RECOVERY_PREFERENCE_KEY")!! } + + private val ignoredUsersPreference by lazy { + findPreference("SETTINGS_IGNORED_USERS_PREFERENCE_KEY")!! + } // private val secureBackupResetPreference by lazy { // findPreference(VectorPreferences.SETTINGS_SECURE_BACKUP_RESET_PREFERENCE_KEY) // } @@ -275,6 +279,11 @@ class VectorSettingsSecurityPrivacyFragment @Inject constructor( ContextCompat.getDrawable(it, R.drawable.ic_secure_backup)!!, R.attr.vctr_content_primary) } + ignoredUsersPreference.icon = activity?.let { + ThemeUtils.tintDrawable(it, + ContextCompat.getDrawable(it, R.drawable.ic_settings_root_ignored_users)!!, R.attr.vctr_content_primary) + } + findPreference(VectorPreferences.SETTINGS_CRYPTOGRAPHY_HS_ADMIN_DISABLED_E2E_DEFAULT)?.let { it.icon = ThemeUtils.tintDrawableWithColor( ContextCompat.getDrawable(requireContext(), R.drawable.ic_notification_privacy_warning)!!, diff --git a/vector/src/main/java/im/vector/app/features/settings/VectorSettingsVoiceVideoFragment.kt b/vector/src/main/java/im/vector/app/features/settings/VectorSettingsVoiceVideoFragment.kt index 05d1a1f60f..fbf54479fc 100644 --- a/vector/src/main/java/im/vector/app/features/settings/VectorSettingsVoiceVideoFragment.kt +++ b/vector/src/main/java/im/vector/app/features/settings/VectorSettingsVoiceVideoFragment.kt @@ -20,6 +20,7 @@ import android.app.Activity import android.content.Intent import android.media.RingtoneManager import android.net.Uri +import android.os.Bundle import androidx.preference.Preference import androidx.preference.SwitchPreference import im.vector.app.R @@ -29,6 +30,7 @@ import im.vector.app.core.utils.getCallRingtoneName import im.vector.app.core.utils.getCallRingtoneUri import im.vector.app.core.utils.setCallRingtoneUri import im.vector.app.core.utils.setUseRiotDefaultRingtone +import im.vector.app.features.analytics.plan.MobileScreen class VectorSettingsVoiceVideoFragment : VectorSettingsBaseFragment() { @@ -42,6 +44,11 @@ class VectorSettingsVoiceVideoFragment : VectorSettingsBaseFragment() { findPreference(VectorPreferences.SETTINGS_CALL_RINGTONE_URI_PREFERENCE_KEY)!! } + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + analyticsScreenName = MobileScreen.ScreenName.SettingsVoiceVideo + } + override fun bindPref() { // Incoming call sounds mUseRiotCallRingtonePreference.onPreferenceClickListener = Preference.OnPreferenceClickListener { diff --git a/vector/src/main/java/im/vector/app/features/settings/account/deactivation/DeactivateAccountFragment.kt b/vector/src/main/java/im/vector/app/features/settings/account/deactivation/DeactivateAccountFragment.kt index 631c375e62..4397da00c4 100644 --- a/vector/src/main/java/im/vector/app/features/settings/account/deactivation/DeactivateAccountFragment.kt +++ b/vector/src/main/java/im/vector/app/features/settings/account/deactivation/DeactivateAccountFragment.kt @@ -25,7 +25,6 @@ import android.view.ViewGroup import androidx.appcompat.app.AppCompatActivity import com.airbnb.mvrx.fragmentViewModel import im.vector.app.R -import im.vector.app.core.extensions.exhaustive import im.vector.app.core.extensions.registerStartForActivityResult import im.vector.app.core.platform.VectorBaseFragment import im.vector.app.databinding.FragmentDeactivateAccountBinding @@ -128,7 +127,7 @@ class DeactivateAccountFragment @Inject constructor() : VectorBaseFragment(initialState) { + VectorViewModel(initialState) { @AssistedFactory interface Factory : MavericksAssistedViewModelFactory { @@ -58,7 +57,7 @@ class DeactivateAccountViewModel @AssistedInject constructor(@Assisted private v override fun handle(action: DeactivateAccountAction) { when (action) { is DeactivateAccountAction.DeactivateAccount -> handleDeactivateAccount(action) - DeactivateAccountAction.SsoAuthDone -> { + DeactivateAccountAction.SsoAuthDone -> { Timber.d("## UIA - FallBack success") if (pendingAuth != null) { uiaContinuation?.resume(pendingAuth!!) @@ -66,8 +65,9 @@ class DeactivateAccountViewModel @AssistedInject constructor(@Assisted private v uiaContinuation?.resumeWithException(IllegalArgumentException()) } } - is DeactivateAccountAction.PasswordAuthDone -> { - val decryptedPass = session.loadSecureSecret(action.password.fromBase64().inputStream(), ReAuthActivity.DEFAULT_RESULT_KEYSTORE_ALIAS) + is DeactivateAccountAction.PasswordAuthDone -> { + val decryptedPass = session.secureStorageService() + .loadSecureSecret(action.password.fromBase64().inputStream(), ReAuthActivity.DEFAULT_RESULT_KEYSTORE_ALIAS) uiaContinuation?.resume( UserPasswordAuth( session = pendingAuth?.session, @@ -76,13 +76,13 @@ class DeactivateAccountViewModel @AssistedInject constructor(@Assisted private v ) ) } - DeactivateAccountAction.ReAuthCancelled -> { + DeactivateAccountAction.ReAuthCancelled -> { Timber.d("## UIA - Reauth cancelled") uiaContinuation?.resumeWithException(Exception()) uiaContinuation = null pendingAuth = null } - }.exhaustive + } } private fun handleDeactivateAccount(action: DeactivateAccountAction.DeactivateAccount) { @@ -90,7 +90,7 @@ class DeactivateAccountViewModel @AssistedInject constructor(@Assisted private v viewModelScope.launch { val event = try { - session.deactivateAccount( + session.accountService().deactivateAccount( action.eraseAllData, object : UserInteractiveAuthInterceptor { override fun performStage(flowResponse: RegistrationFlowResponse, errCode: String?, promise: Continuation) { diff --git a/vector/src/main/java/im/vector/app/features/settings/crosssigning/CrossSigningSettingsFragment.kt b/vector/src/main/java/im/vector/app/features/settings/crosssigning/CrossSigningSettingsFragment.kt index fa061cdf8d..51ab422935 100644 --- a/vector/src/main/java/im/vector/app/features/settings/crosssigning/CrossSigningSettingsFragment.kt +++ b/vector/src/main/java/im/vector/app/features/settings/crosssigning/CrossSigningSettingsFragment.kt @@ -28,7 +28,6 @@ import com.google.android.material.dialog.MaterialAlertDialogBuilder import im.vector.app.R import im.vector.app.core.extensions.cleanup import im.vector.app.core.extensions.configureWith -import im.vector.app.core.extensions.exhaustive import im.vector.app.core.extensions.registerStartForActivityResult import im.vector.app.core.extensions.setTextOrHide import im.vector.app.core.platform.VectorBaseFragment @@ -100,7 +99,7 @@ class CrossSigningSettingsFragment @Inject constructor( CrossSigningSettingsViewEvents.HideModalWaitingView -> { views.waitingView.waitingView.isVisible = false } - }.exhaustive + } } } diff --git a/vector/src/main/java/im/vector/app/features/settings/crosssigning/CrossSigningSettingsViewModel.kt b/vector/src/main/java/im/vector/app/features/settings/crosssigning/CrossSigningSettingsViewModel.kt index 644b7f33dd..94d6b8ff93 100644 --- a/vector/src/main/java/im/vector/app/features/settings/crosssigning/CrossSigningSettingsViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/settings/crosssigning/CrossSigningSettingsViewModel.kt @@ -22,7 +22,6 @@ import dagger.assisted.AssistedInject import im.vector.app.R import im.vector.app.core.di.MavericksAssistedViewModelFactory import im.vector.app.core.di.hiltMavericksViewModelFactory -import im.vector.app.core.extensions.exhaustive import im.vector.app.core.platform.VectorViewModel import im.vector.app.core.resources.StringProvider import im.vector.app.features.auth.ReAuthActivity @@ -37,11 +36,11 @@ import org.matrix.android.sdk.api.auth.data.LoginFlowTypes import org.matrix.android.sdk.api.auth.registration.RegistrationFlowResponse import org.matrix.android.sdk.api.auth.registration.nextUncompletedStage import org.matrix.android.sdk.api.session.Session +import org.matrix.android.sdk.api.session.crypto.crosssigning.isVerified +import org.matrix.android.sdk.api.session.uia.DefaultBaseAuth +import org.matrix.android.sdk.api.util.awaitCallback +import org.matrix.android.sdk.api.util.fromBase64 import org.matrix.android.sdk.flow.flow -import org.matrix.android.sdk.internal.crypto.crosssigning.fromBase64 -import org.matrix.android.sdk.internal.crypto.crosssigning.isVerified -import org.matrix.android.sdk.internal.crypto.model.rest.DefaultBaseAuth -import org.matrix.android.sdk.internal.util.awaitCallback import timber.log.Timber import kotlin.coroutines.Continuation import kotlin.coroutines.resume @@ -130,7 +129,8 @@ class CrossSigningSettingsViewModel @AssistedInject constructor( } } is CrossSigningSettingsAction.PasswordAuthDone -> { - val decryptedPass = session.loadSecureSecret(action.password.fromBase64().inputStream(), ReAuthActivity.DEFAULT_RESULT_KEYSTORE_ALIAS) + val decryptedPass = session.secureStorageService() + .loadSecureSecret(action.password.fromBase64().inputStream(), ReAuthActivity.DEFAULT_RESULT_KEYSTORE_ALIAS) uiaContinuation?.resume( UserPasswordAuth( session = pendingAuth?.session, @@ -146,7 +146,7 @@ class CrossSigningSettingsViewModel @AssistedInject constructor( uiaContinuation = null pendingAuth = null } - }.exhaustive + } } private fun handleInitializeXSigningError(failure: Throwable) { diff --git a/vector/src/main/java/im/vector/app/features/settings/devices/DeviceItem.kt b/vector/src/main/java/im/vector/app/features/settings/devices/DeviceItem.kt index ad8cdb7791..b0f6261424 100644 --- a/vector/src/main/java/im/vector/app/features/settings/devices/DeviceItem.kt +++ b/vector/src/main/java/im/vector/app/features/settings/devices/DeviceItem.kt @@ -31,8 +31,8 @@ import im.vector.app.core.resources.ColorProvider import im.vector.app.core.ui.views.ShieldImageView import im.vector.app.core.utils.DimensionConverter import me.gujun.android.span.span -import org.matrix.android.sdk.internal.crypto.crosssigning.DeviceTrustLevel -import org.matrix.android.sdk.internal.crypto.model.rest.DeviceInfo +import org.matrix.android.sdk.api.session.crypto.crosssigning.DeviceTrustLevel +import org.matrix.android.sdk.api.session.crypto.model.DeviceInfo /** * A list item for Device. diff --git a/vector/src/main/java/im/vector/app/features/settings/devices/DeviceVerificationInfoBottomSheetController.kt b/vector/src/main/java/im/vector/app/features/settings/devices/DeviceVerificationInfoBottomSheetController.kt index 2b8fa4b49a..8f04534440 100644 --- a/vector/src/main/java/im/vector/app/features/settings/devices/DeviceVerificationInfoBottomSheetController.kt +++ b/vector/src/main/java/im/vector/app/features/settings/devices/DeviceVerificationInfoBottomSheetController.kt @@ -28,7 +28,7 @@ import im.vector.app.core.ui.views.toDrawableRes import im.vector.app.features.crypto.verification.epoxy.bottomSheetVerificationActionItem import im.vector.lib.core.utils.epoxy.charsequence.toEpoxyCharSequence import org.matrix.android.sdk.api.extensions.orFalse -import org.matrix.android.sdk.internal.crypto.model.CryptoDeviceInfo +import org.matrix.android.sdk.api.session.crypto.model.CryptoDeviceInfo import timber.log.Timber import javax.inject.Inject diff --git a/vector/src/main/java/im/vector/app/features/settings/devices/DeviceVerificationInfoBottomSheetViewModel.kt b/vector/src/main/java/im/vector/app/features/settings/devices/DeviceVerificationInfoBottomSheetViewModel.kt index 3a944b5a71..b24b8475a5 100644 --- a/vector/src/main/java/im/vector/app/features/settings/devices/DeviceVerificationInfoBottomSheetViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/settings/devices/DeviceVerificationInfoBottomSheetViewModel.kt @@ -27,8 +27,8 @@ import im.vector.app.core.platform.EmptyViewEvents import im.vector.app.core.platform.VectorViewModel import kotlinx.coroutines.flow.map import org.matrix.android.sdk.api.session.Session +import org.matrix.android.sdk.api.session.crypto.model.DeviceInfo import org.matrix.android.sdk.flow.flow -import org.matrix.android.sdk.internal.crypto.model.rest.DeviceInfo class DeviceVerificationInfoBottomSheetViewModel @AssistedInject constructor(@Assisted initialState: DeviceVerificationInfoBottomSheetViewState, val session: Session @@ -48,7 +48,7 @@ class DeviceVerificationInfoBottomSheetViewModel @AssistedInject constructor(@As copy( hasAccountCrossSigning = session.cryptoService().crossSigningService().isCrossSigningInitialized(), accountCrossSigningIsTrusted = session.cryptoService().crossSigningService().isCrossSigningVerified(), - isRecoverySetup = session.sharedSecretStorageService.isRecoverySetup() + isRecoverySetup = session.sharedSecretStorageService().isRecoverySetup() ) } session.flow().liveCrossSigningInfo(session.myUserId) diff --git a/vector/src/main/java/im/vector/app/features/settings/devices/DeviceVerificationInfoBottomSheetViewState.kt b/vector/src/main/java/im/vector/app/features/settings/devices/DeviceVerificationInfoBottomSheetViewState.kt index 32927ca068..d0c236d119 100644 --- a/vector/src/main/java/im/vector/app/features/settings/devices/DeviceVerificationInfoBottomSheetViewState.kt +++ b/vector/src/main/java/im/vector/app/features/settings/devices/DeviceVerificationInfoBottomSheetViewState.kt @@ -19,8 +19,8 @@ package im.vector.app.features.settings.devices import com.airbnb.mvrx.Async import com.airbnb.mvrx.MavericksState import com.airbnb.mvrx.Uninitialized -import org.matrix.android.sdk.internal.crypto.model.CryptoDeviceInfo -import org.matrix.android.sdk.internal.crypto.model.rest.DeviceInfo +import org.matrix.android.sdk.api.session.crypto.model.CryptoDeviceInfo +import org.matrix.android.sdk.api.session.crypto.model.DeviceInfo data class DeviceVerificationInfoBottomSheetViewState( val deviceId: String, diff --git a/vector/src/main/java/im/vector/app/features/settings/devices/DevicesAction.kt b/vector/src/main/java/im/vector/app/features/settings/devices/DevicesAction.kt index e402982d97..8ee0e7636e 100644 --- a/vector/src/main/java/im/vector/app/features/settings/devices/DevicesAction.kt +++ b/vector/src/main/java/im/vector/app/features/settings/devices/DevicesAction.kt @@ -17,13 +17,13 @@ package im.vector.app.features.settings.devices import im.vector.app.core.platform.VectorViewModelAction -import org.matrix.android.sdk.internal.crypto.model.CryptoDeviceInfo +import org.matrix.android.sdk.api.session.crypto.model.CryptoDeviceInfo sealed class DevicesAction : VectorViewModelAction { object Refresh : DevicesAction() data class Delete(val deviceId: String) : DevicesAction() -// data class Password(val password: String) : DevicesAction() + // data class Password(val password: String) : DevicesAction() data class Rename(val deviceId: String, val newName: String) : DevicesAction() data class PromptRename(val deviceId: String) : DevicesAction() diff --git a/vector/src/main/java/im/vector/app/features/settings/devices/DevicesController.kt b/vector/src/main/java/im/vector/app/features/settings/devices/DevicesController.kt index ab63ad0894..2659ff4966 100644 --- a/vector/src/main/java/im/vector/app/features/settings/devices/DevicesController.kt +++ b/vector/src/main/java/im/vector/app/features/settings/devices/DevicesController.kt @@ -32,8 +32,8 @@ import im.vector.app.core.resources.StringProvider import im.vector.app.core.ui.list.genericHeaderItem import im.vector.app.core.utils.DimensionConverter import im.vector.app.features.settings.VectorPreferences -import org.matrix.android.sdk.internal.crypto.crosssigning.DeviceTrustLevel -import org.matrix.android.sdk.internal.crypto.model.rest.DeviceInfo +import org.matrix.android.sdk.api.session.crypto.crosssigning.DeviceTrustLevel +import org.matrix.android.sdk.api.session.crypto.model.DeviceInfo import javax.inject.Inject class DevicesController @Inject constructor(private val errorFormatter: ErrorFormatter, diff --git a/vector/src/main/java/im/vector/app/features/settings/devices/DevicesViewEvents.kt b/vector/src/main/java/im/vector/app/features/settings/devices/DevicesViewEvents.kt index 9e1f83582f..8ba7dbc871 100644 --- a/vector/src/main/java/im/vector/app/features/settings/devices/DevicesViewEvents.kt +++ b/vector/src/main/java/im/vector/app/features/settings/devices/DevicesViewEvents.kt @@ -20,8 +20,8 @@ package im.vector.app.features.settings.devices import im.vector.app.core.platform.VectorViewEvents import org.matrix.android.sdk.api.auth.registration.RegistrationFlowResponse import org.matrix.android.sdk.api.session.Session -import org.matrix.android.sdk.internal.crypto.model.CryptoDeviceInfo -import org.matrix.android.sdk.internal.crypto.model.rest.DeviceInfo +import org.matrix.android.sdk.api.session.crypto.model.CryptoDeviceInfo +import org.matrix.android.sdk.api.session.crypto.model.DeviceInfo /** * Transient events for Ignored users screen @@ -29,7 +29,7 @@ import org.matrix.android.sdk.internal.crypto.model.rest.DeviceInfo sealed class DevicesViewEvents : VectorViewEvents { data class Loading(val message: CharSequence? = null) : DevicesViewEvents() -// object HideLoading : DevicesViewEvents() + // object HideLoading : DevicesViewEvents() data class Failure(val throwable: Throwable) : DevicesViewEvents() // object RequestPassword : DevicesViewEvents() diff --git a/vector/src/main/java/im/vector/app/features/settings/devices/DevicesViewModel.kt b/vector/src/main/java/im/vector/app/features/settings/devices/DevicesViewModel.kt index 76e82e69f6..4558c4bfb4 100644 --- a/vector/src/main/java/im/vector/app/features/settings/devices/DevicesViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/settings/devices/DevicesViewModel.kt @@ -53,17 +53,17 @@ import org.matrix.android.sdk.api.auth.registration.RegistrationFlowResponse import org.matrix.android.sdk.api.auth.registration.nextUncompletedStage import org.matrix.android.sdk.api.failure.Failure import org.matrix.android.sdk.api.session.Session +import org.matrix.android.sdk.api.session.crypto.crosssigning.DeviceTrustLevel +import org.matrix.android.sdk.api.session.crypto.model.CryptoDeviceInfo +import org.matrix.android.sdk.api.session.crypto.model.DeviceInfo import org.matrix.android.sdk.api.session.crypto.verification.VerificationMethod import org.matrix.android.sdk.api.session.crypto.verification.VerificationService import org.matrix.android.sdk.api.session.crypto.verification.VerificationTransaction import org.matrix.android.sdk.api.session.crypto.verification.VerificationTxState +import org.matrix.android.sdk.api.session.uia.DefaultBaseAuth +import org.matrix.android.sdk.api.util.awaitCallback +import org.matrix.android.sdk.api.util.fromBase64 import org.matrix.android.sdk.flow.flow -import org.matrix.android.sdk.internal.crypto.crosssigning.DeviceTrustLevel -import org.matrix.android.sdk.internal.crypto.crosssigning.fromBase64 -import org.matrix.android.sdk.internal.crypto.model.CryptoDeviceInfo -import org.matrix.android.sdk.internal.crypto.model.rest.DefaultBaseAuth -import org.matrix.android.sdk.internal.crypto.model.rest.DeviceInfo -import org.matrix.android.sdk.internal.util.awaitCallback import timber.log.Timber import javax.net.ssl.HttpsURLConnection import kotlin.coroutines.Continuation @@ -219,7 +219,8 @@ class DevicesViewModel @AssistedInject constructor( Unit } is DevicesAction.PasswordAuthDone -> { - val decryptedPass = session.loadSecureSecret(action.password.fromBase64().inputStream(), ReAuthActivity.DEFAULT_RESULT_KEYSTORE_ALIAS) + val decryptedPass = session.secureStorageService() + .loadSecureSecret(action.password.fromBase64().inputStream(), ReAuthActivity.DEFAULT_RESULT_KEYSTORE_ALIAS) uiaContinuation?.resume( UserPasswordAuth( session = pendingAuth?.session, diff --git a/vector/src/main/java/im/vector/app/features/settings/devices/TrustUtils.kt b/vector/src/main/java/im/vector/app/features/settings/devices/TrustUtils.kt index 06ef96daf7..eadf020f7e 100644 --- a/vector/src/main/java/im/vector/app/features/settings/devices/TrustUtils.kt +++ b/vector/src/main/java/im/vector/app/features/settings/devices/TrustUtils.kt @@ -16,8 +16,8 @@ package im.vector.app.features.settings.devices -import org.matrix.android.sdk.api.crypto.RoomEncryptionTrustLevel -import org.matrix.android.sdk.internal.crypto.crosssigning.DeviceTrustLevel +import org.matrix.android.sdk.api.session.crypto.crosssigning.DeviceTrustLevel +import org.matrix.android.sdk.api.session.crypto.model.RoomEncryptionTrustLevel object TrustUtils { diff --git a/vector/src/main/java/im/vector/app/features/settings/devices/VectorSettingsDevicesFragment.kt b/vector/src/main/java/im/vector/app/features/settings/devices/VectorSettingsDevicesFragment.kt index 5bbb03c8a4..e897cdccac 100644 --- a/vector/src/main/java/im/vector/app/features/settings/devices/VectorSettingsDevicesFragment.kt +++ b/vector/src/main/java/im/vector/app/features/settings/devices/VectorSettingsDevicesFragment.kt @@ -32,7 +32,6 @@ import im.vector.app.R import im.vector.app.core.dialogs.ManuallyVerifyDialog import im.vector.app.core.extensions.cleanup import im.vector.app.core.extensions.configureWith -import im.vector.app.core.extensions.exhaustive import im.vector.app.core.extensions.registerStartForActivityResult import im.vector.app.core.platform.VectorBaseFragment import im.vector.app.databinding.DialogBaseEditTextBinding @@ -40,7 +39,7 @@ import im.vector.app.databinding.FragmentGenericRecyclerBinding import im.vector.app.features.auth.ReAuthActivity import im.vector.app.features.crypto.verification.VerificationBottomSheet import org.matrix.android.sdk.api.auth.data.LoginFlowTypes -import org.matrix.android.sdk.internal.crypto.model.rest.DeviceInfo +import org.matrix.android.sdk.api.session.crypto.model.DeviceInfo import javax.inject.Inject /** @@ -90,7 +89,7 @@ class VectorSettingsDevicesFragment @Inject constructor( viewModel.handle(DevicesAction.MarkAsManuallyVerified(it.cryptoDeviceInfo)) } } - }.exhaustive + } } } diff --git a/vector/src/main/java/im/vector/app/features/settings/devtools/AccountDataEpoxyController.kt b/vector/src/main/java/im/vector/app/features/settings/devtools/AccountDataEpoxyController.kt index f3ae18a72f..e840ab2266 100644 --- a/vector/src/main/java/im/vector/app/features/settings/devtools/AccountDataEpoxyController.kt +++ b/vector/src/main/java/im/vector/app/features/settings/devtools/AccountDataEpoxyController.kt @@ -80,6 +80,7 @@ class AccountDataEpoxyController @Inject constructor( } } } + else -> Unit } } } diff --git a/vector/src/main/java/im/vector/app/features/settings/devtools/AccountDataFragment.kt b/vector/src/main/java/im/vector/app/features/settings/devtools/AccountDataFragment.kt index bce15bbf4b..740ef3996a 100644 --- a/vector/src/main/java/im/vector/app/features/settings/devtools/AccountDataFragment.kt +++ b/vector/src/main/java/im/vector/app/features/settings/devtools/AccountDataFragment.kt @@ -33,7 +33,7 @@ import im.vector.app.core.utils.createJSonViewerStyleProvider import im.vector.app.databinding.FragmentGenericRecyclerBinding import org.billcarsonfr.jsonviewer.JSonViewerDialog import org.matrix.android.sdk.api.session.accountdata.UserAccountDataEvent -import org.matrix.android.sdk.internal.di.MoshiProvider +import org.matrix.android.sdk.api.util.MatrixJsonParser import javax.inject.Inject class AccountDataFragment @Inject constructor( @@ -70,7 +70,7 @@ class AccountDataFragment @Inject constructor( } override fun didTap(data: UserAccountDataEvent) { - val jsonString = MoshiProvider.providesMoshi() + val jsonString = MatrixJsonParser.getMoshi() .adapter(UserAccountDataEvent::class.java) .toJson(data) JSonViewerDialog.newInstance( diff --git a/vector/src/main/java/im/vector/app/features/settings/devtools/AccountDataViewModel.kt b/vector/src/main/java/im/vector/app/features/settings/devtools/AccountDataViewModel.kt index 6289699687..72085d37fd 100644 --- a/vector/src/main/java/im/vector/app/features/settings/devtools/AccountDataViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/settings/devtools/AccountDataViewModel.kt @@ -25,7 +25,6 @@ import dagger.assisted.AssistedFactory import dagger.assisted.AssistedInject import im.vector.app.core.di.MavericksAssistedViewModelFactory import im.vector.app.core.di.hiltMavericksViewModelFactory -import im.vector.app.core.extensions.exhaustive import im.vector.app.core.platform.EmptyViewEvents import im.vector.app.core.platform.VectorViewModel import kotlinx.coroutines.launch @@ -39,7 +38,7 @@ data class AccountDataViewState( class AccountDataViewModel @AssistedInject constructor(@Assisted initialState: AccountDataViewState, private val session: Session) : - VectorViewModel(initialState) { + VectorViewModel(initialState) { init { session.flow().liveUserAccountData(emptySet()) @@ -51,7 +50,7 @@ class AccountDataViewModel @AssistedInject constructor(@Assisted initialState: A override fun handle(action: AccountDataAction) { when (action) { is AccountDataAction.DeleteAccountData -> handleDeleteAccountData(action) - }.exhaustive + } } private fun handleDeleteAccountData(action: AccountDataAction.DeleteAccountData) { diff --git a/vector/src/main/java/im/vector/app/features/settings/devtools/GossipingEventsPaperTrailViewModel.kt b/vector/src/main/java/im/vector/app/features/settings/devtools/GossipingEventsPaperTrailViewModel.kt index dde032d303..30c2757eff 100644 --- a/vector/src/main/java/im/vector/app/features/settings/devtools/GossipingEventsPaperTrailViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/settings/devtools/GossipingEventsPaperTrailViewModel.kt @@ -40,7 +40,7 @@ data class GossipingEventsPaperTrailState( class GossipingEventsPaperTrailViewModel @AssistedInject constructor(@Assisted initialState: GossipingEventsPaperTrailState, private val session: Session) : - VectorViewModel(initialState) { + VectorViewModel(initialState) { init { refresh() diff --git a/vector/src/main/java/im/vector/app/features/settings/devtools/GossipingEventsSerializer.kt b/vector/src/main/java/im/vector/app/features/settings/devtools/GossipingEventsSerializer.kt index d18a6c2ba8..30c2efc3ce 100644 --- a/vector/src/main/java/im/vector/app/features/settings/devtools/GossipingEventsSerializer.kt +++ b/vector/src/main/java/im/vector/app/features/settings/devtools/GossipingEventsSerializer.kt @@ -18,15 +18,15 @@ package im.vector.app.features.settings.devtools import im.vector.app.core.resources.DateProvider import me.gujun.android.span.span +import org.matrix.android.sdk.api.session.crypto.model.ForwardedRoomKeyContent +import org.matrix.android.sdk.api.session.crypto.model.GossipingToDeviceObject +import org.matrix.android.sdk.api.session.crypto.model.RoomKeyShareRequest +import org.matrix.android.sdk.api.session.crypto.model.SecretShareRequest import org.matrix.android.sdk.api.session.events.model.Event import org.matrix.android.sdk.api.session.events.model.EventType +import org.matrix.android.sdk.api.session.events.model.content.OlmEventContent +import org.matrix.android.sdk.api.session.events.model.content.SecretSendEventContent import org.matrix.android.sdk.api.session.events.model.toModel -import org.matrix.android.sdk.internal.crypto.model.event.OlmEventContent -import org.matrix.android.sdk.internal.crypto.model.event.SecretSendEventContent -import org.matrix.android.sdk.internal.crypto.model.rest.ForwardedRoomKeyContent -import org.matrix.android.sdk.internal.crypto.model.rest.GossipingToDeviceObject -import org.matrix.android.sdk.internal.crypto.model.rest.RoomKeyShareRequest -import org.matrix.android.sdk.internal.crypto.model.rest.SecretShareRequest import org.threeten.bp.format.DateTimeFormatter class GossipingEventsSerializer { diff --git a/vector/src/main/java/im/vector/app/features/settings/devtools/GossipingTrailPagedEpoxyController.kt b/vector/src/main/java/im/vector/app/features/settings/devtools/GossipingTrailPagedEpoxyController.kt index c1b05cca42..dd016c2bf5 100644 --- a/vector/src/main/java/im/vector/app/features/settings/devtools/GossipingTrailPagedEpoxyController.kt +++ b/vector/src/main/java/im/vector/app/features/settings/devtools/GossipingTrailPagedEpoxyController.kt @@ -26,15 +26,15 @@ import im.vector.app.core.ui.list.GenericItem_ import im.vector.app.core.utils.createUIHandler import im.vector.lib.core.utils.epoxy.charsequence.toEpoxyCharSequence import me.gujun.android.span.span +import org.matrix.android.sdk.api.session.crypto.model.ForwardedRoomKeyContent +import org.matrix.android.sdk.api.session.crypto.model.GossipingToDeviceObject +import org.matrix.android.sdk.api.session.crypto.model.RoomKeyShareRequest +import org.matrix.android.sdk.api.session.crypto.model.SecretShareRequest import org.matrix.android.sdk.api.session.events.model.Event import org.matrix.android.sdk.api.session.events.model.EventType +import org.matrix.android.sdk.api.session.events.model.content.OlmEventContent +import org.matrix.android.sdk.api.session.events.model.content.SecretSendEventContent import org.matrix.android.sdk.api.session.events.model.toModel -import org.matrix.android.sdk.internal.crypto.model.event.OlmEventContent -import org.matrix.android.sdk.internal.crypto.model.event.SecretSendEventContent -import org.matrix.android.sdk.internal.crypto.model.rest.ForwardedRoomKeyContent -import org.matrix.android.sdk.internal.crypto.model.rest.GossipingToDeviceObject -import org.matrix.android.sdk.internal.crypto.model.rest.RoomKeyShareRequest -import org.matrix.android.sdk.internal.crypto.model.rest.SecretShareRequest import javax.inject.Inject class GossipingTrailPagedEpoxyController @Inject constructor( diff --git a/vector/src/main/java/im/vector/app/features/settings/devtools/IncomingKeyRequestPagedController.kt b/vector/src/main/java/im/vector/app/features/settings/devtools/IncomingKeyRequestPagedController.kt index 4c8bd65c0e..4653f04f2c 100644 --- a/vector/src/main/java/im/vector/app/features/settings/devtools/IncomingKeyRequestPagedController.kt +++ b/vector/src/main/java/im/vector/app/features/settings/devtools/IncomingKeyRequestPagedController.kt @@ -24,7 +24,7 @@ import im.vector.app.core.ui.list.GenericItem_ import im.vector.app.core.utils.createUIHandler import im.vector.lib.core.utils.epoxy.charsequence.toEpoxyCharSequence import me.gujun.android.span.span -import org.matrix.android.sdk.internal.crypto.IncomingRoomKeyRequest +import org.matrix.android.sdk.api.session.crypto.model.IncomingRoomKeyRequest import javax.inject.Inject class IncomingKeyRequestPagedController @Inject constructor( diff --git a/vector/src/main/java/im/vector/app/features/settings/devtools/KeyRequestListViewModel.kt b/vector/src/main/java/im/vector/app/features/settings/devtools/KeyRequestListViewModel.kt index 197a72cb05..a8045c07e3 100644 --- a/vector/src/main/java/im/vector/app/features/settings/devtools/KeyRequestListViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/settings/devtools/KeyRequestListViewModel.kt @@ -32,8 +32,8 @@ import im.vector.app.core.platform.EmptyViewEvents import im.vector.app.core.platform.VectorViewModel import kotlinx.coroutines.launch import org.matrix.android.sdk.api.session.Session -import org.matrix.android.sdk.internal.crypto.IncomingRoomKeyRequest -import org.matrix.android.sdk.internal.crypto.OutgoingRoomKeyRequest +import org.matrix.android.sdk.api.session.crypto.model.IncomingRoomKeyRequest +import org.matrix.android.sdk.api.session.crypto.model.OutgoingRoomKeyRequest data class KeyRequestListViewState( val incomingRequests: Async> = Uninitialized, diff --git a/vector/src/main/java/im/vector/app/features/settings/devtools/KeyRequestViewModel.kt b/vector/src/main/java/im/vector/app/features/settings/devtools/KeyRequestViewModel.kt index f480eb2db8..f2df0e4211 100644 --- a/vector/src/main/java/im/vector/app/features/settings/devtools/KeyRequestViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/settings/devtools/KeyRequestViewModel.kt @@ -29,7 +29,6 @@ import dagger.assisted.AssistedFactory import dagger.assisted.AssistedInject import im.vector.app.core.di.MavericksAssistedViewModelFactory import im.vector.app.core.di.hiltMavericksViewModelFactory -import im.vector.app.core.extensions.exhaustive import im.vector.app.core.platform.VectorViewEvents import im.vector.app.core.platform.VectorViewModel import im.vector.app.core.platform.VectorViewModelAction @@ -52,7 +51,7 @@ data class KeyRequestViewState( class KeyRequestViewModel @AssistedInject constructor( @Assisted initialState: KeyRequestViewState, private val session: Session) : - VectorViewModel(initialState) { + VectorViewModel(initialState) { @AssistedFactory interface Factory : MavericksAssistedViewModelFactory { @@ -64,7 +63,7 @@ class KeyRequestViewModel @AssistedInject constructor( override fun handle(action: KeyRequestAction) { when (action) { is KeyRequestAction.ExportAudit -> exportAudit(action) - }.exhaustive + } } private fun exportAudit(action: KeyRequestAction.ExportAudit) { diff --git a/vector/src/main/java/im/vector/app/features/settings/devtools/KeyRequestsFragment.kt b/vector/src/main/java/im/vector/app/features/settings/devtools/KeyRequestsFragment.kt index d807fc620a..cef68c01c1 100644 --- a/vector/src/main/java/im/vector/app/features/settings/devtools/KeyRequestsFragment.kt +++ b/vector/src/main/java/im/vector/app/features/settings/devtools/KeyRequestsFragment.kt @@ -33,8 +33,8 @@ import com.airbnb.mvrx.fragmentViewModel import com.airbnb.mvrx.withState import com.google.android.material.tabs.TabLayoutMediator import im.vector.app.R -import im.vector.app.core.extensions.exhaustive import im.vector.app.core.extensions.registerStartForActivityResult +import im.vector.app.core.extensions.safeOpenOutputStream import im.vector.app.core.platform.VectorBaseFragment import im.vector.app.core.utils.selectTxtFileToWrite import im.vector.app.databinding.FragmentDevtoolKeyrequestsBinding @@ -107,11 +107,11 @@ class KeyRequestsFragment @Inject constructor() : VectorBaseFragment { tryOrNull { - requireContext().contentResolver?.openOutputStream(it.uri) + requireContext().safeOpenOutputStream(it.uri) ?.use { os -> os.write(it.raw.toByteArray()) } } } - }.exhaustive + } } } diff --git a/vector/src/main/java/im/vector/app/features/settings/devtools/OutgoingKeyRequestListFragment.kt b/vector/src/main/java/im/vector/app/features/settings/devtools/OutgoingKeyRequestListFragment.kt index 0483d5fb4d..ca1f36dbb2 100644 --- a/vector/src/main/java/im/vector/app/features/settings/devtools/OutgoingKeyRequestListFragment.kt +++ b/vector/src/main/java/im/vector/app/features/settings/devtools/OutgoingKeyRequestListFragment.kt @@ -28,6 +28,7 @@ import im.vector.app.core.extensions.configureWith import im.vector.app.core.platform.VectorBaseFragment import im.vector.app.databinding.FragmentGenericRecyclerBinding import javax.inject.Inject + class OutgoingKeyRequestListFragment @Inject constructor( private val epoxyController: OutgoingKeyRequestPagedController ) : VectorBaseFragment() { diff --git a/vector/src/main/java/im/vector/app/features/settings/devtools/OutgoingKeyRequestPagedController.kt b/vector/src/main/java/im/vector/app/features/settings/devtools/OutgoingKeyRequestPagedController.kt index 0a52c1a7dd..b23bd77277 100644 --- a/vector/src/main/java/im/vector/app/features/settings/devtools/OutgoingKeyRequestPagedController.kt +++ b/vector/src/main/java/im/vector/app/features/settings/devtools/OutgoingKeyRequestPagedController.kt @@ -22,7 +22,7 @@ import im.vector.app.core.ui.list.GenericItem_ import im.vector.app.core.utils.createUIHandler import im.vector.lib.core.utils.epoxy.charsequence.toEpoxyCharSequence import me.gujun.android.span.span -import org.matrix.android.sdk.internal.crypto.OutgoingRoomKeyRequest +import org.matrix.android.sdk.api.session.crypto.model.OutgoingRoomKeyRequest import javax.inject.Inject class OutgoingKeyRequestPagedController @Inject constructor() : PagedListEpoxyController( diff --git a/vector/src/main/java/im/vector/app/features/settings/homeserver/HomeserverSettingsViewModel.kt b/vector/src/main/java/im/vector/app/features/settings/homeserver/HomeserverSettingsViewModel.kt index fab563b49e..9e237f818b 100644 --- a/vector/src/main/java/im/vector/app/features/settings/homeserver/HomeserverSettingsViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/settings/homeserver/HomeserverSettingsViewModel.kt @@ -47,7 +47,7 @@ class HomeserverSettingsViewModel @AssistedInject constructor( copy( homeserverUrl = session.sessionParams.homeServerUrl, homeserverClientServerApiUrl = session.sessionParams.homeServerUrlBase, - homeServerCapabilities = session.getHomeServerCapabilities() + homeServerCapabilities = session.homeServerCapabilitiesService().getHomeServerCapabilities() ) } fetchHomeserverVersion() @@ -57,12 +57,12 @@ class HomeserverSettingsViewModel @AssistedInject constructor( private fun refreshHomeServerCapabilities() { viewModelScope.launch { runCatching { - session.refreshHomeServerCapabilities() + session.homeServerCapabilitiesService().refreshHomeServerCapabilities() } setState { copy( - homeServerCapabilities = session.getHomeServerCapabilities() + homeServerCapabilities = session.homeServerCapabilitiesService().getHomeServerCapabilities() ) } } diff --git a/vector/src/main/java/im/vector/app/features/settings/ignored/IgnoredUsersAction.kt b/vector/src/main/java/im/vector/app/features/settings/ignored/IgnoredUsersAction.kt new file mode 100644 index 0000000000..48199e557b --- /dev/null +++ b/vector/src/main/java/im/vector/app/features/settings/ignored/IgnoredUsersAction.kt @@ -0,0 +1,23 @@ +/* + * Copyright (c) 2022 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package im.vector.app.features.settings.ignored + +import im.vector.app.core.platform.VectorViewModelAction + +sealed class IgnoredUsersAction : VectorViewModelAction { + data class UnIgnore(val userId: String) : IgnoredUsersAction() +} diff --git a/vector/src/main/java/im/vector/app/features/settings/ignored/IgnoredUsersViewEvents.kt b/vector/src/main/java/im/vector/app/features/settings/ignored/IgnoredUsersViewEvents.kt index 2b2c3eb49d..8d597a9189 100644 --- a/vector/src/main/java/im/vector/app/features/settings/ignored/IgnoredUsersViewEvents.kt +++ b/vector/src/main/java/im/vector/app/features/settings/ignored/IgnoredUsersViewEvents.kt @@ -25,4 +25,5 @@ import im.vector.app.core.platform.VectorViewEvents sealed class IgnoredUsersViewEvents : VectorViewEvents { data class Loading(val message: CharSequence? = null) : IgnoredUsersViewEvents() data class Failure(val throwable: Throwable) : IgnoredUsersViewEvents() + object Success : IgnoredUsersViewEvents() } diff --git a/vector/src/main/java/im/vector/app/features/settings/ignored/IgnoredUsersViewModel.kt b/vector/src/main/java/im/vector/app/features/settings/ignored/IgnoredUsersViewModel.kt index b2a7b2cbd1..df76d68c17 100644 --- a/vector/src/main/java/im/vector/app/features/settings/ignored/IgnoredUsersViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/settings/ignored/IgnoredUsersViewModel.kt @@ -16,37 +16,21 @@ package im.vector.app.features.settings.ignored -import com.airbnb.mvrx.Async -import com.airbnb.mvrx.Fail -import com.airbnb.mvrx.Loading -import com.airbnb.mvrx.MavericksState import com.airbnb.mvrx.MavericksViewModelFactory -import com.airbnb.mvrx.Success -import com.airbnb.mvrx.Uninitialized import dagger.assisted.Assisted import dagger.assisted.AssistedFactory import dagger.assisted.AssistedInject import im.vector.app.core.di.MavericksAssistedViewModelFactory import im.vector.app.core.di.hiltMavericksViewModelFactory import im.vector.app.core.platform.VectorViewModel -import im.vector.app.core.platform.VectorViewModelAction import kotlinx.coroutines.launch import org.matrix.android.sdk.api.session.Session -import org.matrix.android.sdk.api.session.user.model.User import org.matrix.android.sdk.flow.flow -data class IgnoredUsersViewState( - val ignoredUsers: List = emptyList(), - val unIgnoreRequest: Async = Uninitialized -) : MavericksState - -sealed class IgnoredUsersAction : VectorViewModelAction { - data class UnIgnore(val userId: String) : IgnoredUsersAction() -} - -class IgnoredUsersViewModel @AssistedInject constructor(@Assisted initialState: IgnoredUsersViewState, - private val session: Session) : - VectorViewModel(initialState) { +class IgnoredUsersViewModel @AssistedInject constructor( + @Assisted initialState: IgnoredUsersViewState, + private val session: Session +) : VectorViewModel(initialState) { @AssistedFactory interface Factory : MavericksAssistedViewModelFactory { @@ -76,20 +60,16 @@ class IgnoredUsersViewModel @AssistedInject constructor(@Assisted initialState: } private fun handleUnIgnore(action: IgnoredUsersAction.UnIgnore) { - setState { - copy( - unIgnoreRequest = Loading() - ) - } - + setState { copy(isLoading = true) } viewModelScope.launch { - val result = runCatching { session.unIgnoreUserIds(listOf(action.userId)) } - setState { - copy( - unIgnoreRequest = result.fold(::Success, ::Fail) - ) + val viewEvent = try { + session.userService().unIgnoreUserIds(listOf(action.userId)) + IgnoredUsersViewEvents.Success + } catch (throwable: Throwable) { + IgnoredUsersViewEvents.Failure(throwable) } - result.onFailure { _viewEvents.post(IgnoredUsersViewEvents.Failure(it)) } + setState { copy(isLoading = false) } + _viewEvents.post(viewEvent) } } } diff --git a/vector/src/main/java/im/vector/app/features/settings/ignored/IgnoredUsersViewState.kt b/vector/src/main/java/im/vector/app/features/settings/ignored/IgnoredUsersViewState.kt new file mode 100644 index 0000000000..3dc1bfe795 --- /dev/null +++ b/vector/src/main/java/im/vector/app/features/settings/ignored/IgnoredUsersViewState.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.features.settings.ignored + +import com.airbnb.mvrx.MavericksState +import org.matrix.android.sdk.api.session.user.model.User + +data class IgnoredUsersViewState( + val ignoredUsers: List = emptyList(), + val isLoading: Boolean = false +) : MavericksState diff --git a/vector/src/main/java/im/vector/app/features/settings/ignored/VectorSettingsIgnoredUsersFragment.kt b/vector/src/main/java/im/vector/app/features/settings/ignored/VectorSettingsIgnoredUsersFragment.kt index 509014492d..3cb1e29016 100644 --- a/vector/src/main/java/im/vector/app/features/settings/ignored/VectorSettingsIgnoredUsersFragment.kt +++ b/vector/src/main/java/im/vector/app/features/settings/ignored/VectorSettingsIgnoredUsersFragment.kt @@ -22,17 +22,15 @@ import android.view.View import android.view.ViewGroup import androidx.appcompat.app.AppCompatActivity import androidx.core.view.isVisible -import com.airbnb.mvrx.Async -import com.airbnb.mvrx.Loading import com.airbnb.mvrx.fragmentViewModel import com.airbnb.mvrx.withState import com.google.android.material.dialog.MaterialAlertDialogBuilder import im.vector.app.R import im.vector.app.core.extensions.cleanup import im.vector.app.core.extensions.configureWith -import im.vector.app.core.extensions.exhaustive import im.vector.app.core.platform.VectorBaseFragment import im.vector.app.databinding.FragmentGenericRecyclerBinding +import im.vector.app.features.analytics.plan.MobileScreen import javax.inject.Inject class VectorSettingsIgnoredUsersFragment @Inject constructor( @@ -46,6 +44,11 @@ class VectorSettingsIgnoredUsersFragment @Inject constructor( private val viewModel: IgnoredUsersViewModel by fragmentViewModel() + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + analyticsScreenName = MobileScreen.ScreenName.SettingsIgnoredUsers + } + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) @@ -57,7 +60,8 @@ class VectorSettingsIgnoredUsersFragment @Inject constructor( when (it) { is IgnoredUsersViewEvents.Loading -> showLoading(it.message) is IgnoredUsersViewEvents.Failure -> showFailure(it.throwable) - }.exhaustive + IgnoredUsersViewEvents.Success -> Unit + } } } @@ -75,11 +79,12 @@ class VectorSettingsIgnoredUsersFragment @Inject constructor( override fun onUserIdClicked(userId: String) { MaterialAlertDialogBuilder(requireActivity()) + .setTitle(R.string.room_participants_action_unignore_title) .setMessage(getString(R.string.settings_unignore_user, userId)) - .setPositiveButton(R.string.yes) { _, _ -> + .setPositiveButton(R.string.unignore) { _, _ -> viewModel.handle(IgnoredUsersAction.UnIgnore(userId)) } - .setNegativeButton(R.string.no, null) + .setNegativeButton(R.string.action_cancel, null) .show() } @@ -89,14 +94,6 @@ class VectorSettingsIgnoredUsersFragment @Inject constructor( override fun invalidate() = withState(viewModel) { state -> ignoredUsersController.update(state) - - handleUnIgnoreRequestStatus(state.unIgnoreRequest) - } - - private fun handleUnIgnoreRequestStatus(unIgnoreRequest: Async) { - views.waitingView.root.isVisible = when (unIgnoreRequest) { - is Loading -> true - else -> false - } + views.waitingView.root.isVisible = state.isLoading } } diff --git a/vector/src/main/java/im/vector/app/features/settings/legals/LegalsFragment.kt b/vector/src/main/java/im/vector/app/features/settings/legals/LegalsFragment.kt index f9b50bdead..9a4090ad1b 100644 --- a/vector/src/main/java/im/vector/app/features/settings/legals/LegalsFragment.kt +++ b/vector/src/main/java/im/vector/app/features/settings/legals/LegalsFragment.kt @@ -30,6 +30,7 @@ import im.vector.app.core.utils.FirstThrottler import im.vector.app.core.utils.displayInWebView import im.vector.app.core.utils.openUrlInChromeCustomTab import im.vector.app.databinding.FragmentGenericRecyclerBinding +import im.vector.app.features.analytics.plan.MobileScreen import im.vector.app.features.discovery.ServerPolicy import im.vector.app.features.settings.VectorSettingsUrls import im.vector.app.openOssLicensesMenuActivity @@ -47,6 +48,11 @@ class LegalsFragment @Inject constructor( private val viewModel by fragmentViewModel(LegalsViewModel::class) private val firstThrottler = FirstThrottler(1000) + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + analyticsScreenName = MobileScreen.ScreenName.SettingsLegals + } + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) diff --git a/vector/src/main/java/im/vector/app/features/settings/legals/LegalsViewModel.kt b/vector/src/main/java/im/vector/app/features/settings/legals/LegalsViewModel.kt index 9d58535490..1497c793c2 100644 --- a/vector/src/main/java/im/vector/app/features/settings/legals/LegalsViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/settings/legals/LegalsViewModel.kt @@ -25,7 +25,6 @@ import dagger.assisted.AssistedInject import im.vector.app.R import im.vector.app.core.di.MavericksAssistedViewModelFactory import im.vector.app.core.di.hiltMavericksViewModelFactory -import im.vector.app.core.extensions.exhaustive import im.vector.app.core.platform.EmptyViewEvents import im.vector.app.core.platform.VectorViewModel import im.vector.app.core.resources.StringProvider @@ -50,7 +49,7 @@ class LegalsViewModel @AssistedInject constructor( override fun handle(action: LegalsAction) { when (action) { LegalsAction.Refresh -> loadData() - }.exhaustive + } } private fun loadData() = withState { state -> diff --git a/vector/src/main/java/im/vector/app/features/settings/locale/LocalePickerController.kt b/vector/src/main/java/im/vector/app/features/settings/locale/LocalePickerController.kt index 4e1c62a4ec..cffef0da7b 100644 --- a/vector/src/main/java/im/vector/app/features/settings/locale/LocalePickerController.kt +++ b/vector/src/main/java/im/vector/app/features/settings/locale/LocalePickerController.kt @@ -17,12 +17,16 @@ package im.vector.app.features.settings.locale import com.airbnb.epoxy.TypedEpoxyController -import com.airbnb.mvrx.Incomplete +import com.airbnb.mvrx.Fail +import com.airbnb.mvrx.Loading import com.airbnb.mvrx.Success +import com.airbnb.mvrx.Uninitialized import im.vector.app.R +import im.vector.app.core.epoxy.errorWithRetryItem import im.vector.app.core.epoxy.loadingItem import im.vector.app.core.epoxy.noResultItem import im.vector.app.core.epoxy.profiles.profileSectionItem +import im.vector.app.core.error.ErrorFormatter import im.vector.app.core.resources.StringProvider import im.vector.app.core.utils.safeCapitalize import im.vector.app.features.settings.VectorLocale @@ -32,7 +36,8 @@ import javax.inject.Inject class LocalePickerController @Inject constructor( private val vectorPreferences: VectorPreferences, - private val stringProvider: StringProvider + private val stringProvider: StringProvider, + private val errorFormatter: ErrorFormatter ) : TypedEpoxyController() { var listener: Listener? = null @@ -58,13 +63,14 @@ class LocalePickerController @Inject constructor( title(host.stringProvider.getString(R.string.choose_locale_other_locales_title)) } when (list) { - is Incomplete -> { + Uninitialized, + is Loading -> { loadingItem { id("loading") loadingText(host.stringProvider.getString(R.string.choose_locale_loading_locales)) } } - is Success -> + is Success -> if (list().isEmpty()) { noResultItem { id("noResult") @@ -84,6 +90,11 @@ class LocalePickerController @Inject constructor( } } } + is Fail -> + errorWithRetryItem { + id("error") + text(host.errorFormatter.toHumanReadable(list.error)) + } } } diff --git a/vector/src/main/java/im/vector/app/features/settings/locale/LocalePickerFragment.kt b/vector/src/main/java/im/vector/app/features/settings/locale/LocalePickerFragment.kt index 601574c908..d46b66dd87 100644 --- a/vector/src/main/java/im/vector/app/features/settings/locale/LocalePickerFragment.kt +++ b/vector/src/main/java/im/vector/app/features/settings/locale/LocalePickerFragment.kt @@ -26,7 +26,6 @@ import com.airbnb.mvrx.withState import im.vector.app.R import im.vector.app.core.extensions.cleanup import im.vector.app.core.extensions.configureWith -import im.vector.app.core.extensions.exhaustive import im.vector.app.core.extensions.restart import im.vector.app.core.platform.VectorBaseFragment import im.vector.app.databinding.FragmentLocalePickerBinding @@ -54,7 +53,7 @@ class LocalePickerFragment @Inject constructor( LocalePickerViewEvents.RestartActivity -> { activity?.restart() } - }.exhaustive + } } } diff --git a/vector/src/main/java/im/vector/app/features/settings/locale/LocalePickerViewModel.kt b/vector/src/main/java/im/vector/app/features/settings/locale/LocalePickerViewModel.kt index d6b35fa4fe..0bbbc323e0 100644 --- a/vector/src/main/java/im/vector/app/features/settings/locale/LocalePickerViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/settings/locale/LocalePickerViewModel.kt @@ -23,7 +23,6 @@ import dagger.assisted.AssistedFactory import dagger.assisted.AssistedInject import im.vector.app.core.di.MavericksAssistedViewModelFactory import im.vector.app.core.di.hiltMavericksViewModelFactory -import im.vector.app.core.extensions.exhaustive import im.vector.app.core.platform.VectorViewModel import im.vector.app.features.configuration.VectorConfiguration import im.vector.app.features.settings.VectorLocale @@ -56,7 +55,7 @@ class LocalePickerViewModel @AssistedInject constructor( override fun handle(action: LocalePickerAction) { when (action) { is LocalePickerAction.SelectLocale -> handleSelectLocale(action) - }.exhaustive + } } private fun handleSelectLocale(action: LocalePickerAction.SelectLocale) { diff --git a/vector/src/main/java/im/vector/app/features/settings/notifications/NotificationIndex.kt b/vector/src/main/java/im/vector/app/features/settings/notifications/NotificationIndex.kt index 793b94db1d..a768e443bb 100644 --- a/vector/src/main/java/im/vector/app/features/settings/notifications/NotificationIndex.kt +++ b/vector/src/main/java/im/vector/app/features/settings/notifications/NotificationIndex.kt @@ -16,8 +16,8 @@ package im.vector.app.features.settings.notifications -import org.matrix.android.sdk.api.pushrules.rest.PushRule -import org.matrix.android.sdk.api.pushrules.toJson +import org.matrix.android.sdk.api.session.pushrules.rest.PushRule +import org.matrix.android.sdk.api.session.pushrules.toJson enum class NotificationIndex { OFF, @@ -29,15 +29,16 @@ enum class NotificationIndex { * Given a push rule determine the NotificationIndex by comparing it to the static push rule definitions. * Used when determining the selected state of the PushRulePreference. */ -val PushRule.notificationIndex: NotificationIndex? get() = - NotificationIndex.values().firstOrNull { - // Get the actions for the index - val standardAction = getStandardAction(this.ruleId, it) ?: return@firstOrNull false - val indexActions = standardAction.actions ?: listOf() - // Check if the input rule matches a rule generated from the static rule definitions - val targetRule = this.copy(enabled = standardAction != StandardActions.Disabled, actions = indexActions.toJson()) - ruleMatches(this, targetRule) - } +val PushRule.notificationIndex: NotificationIndex? + get() = + NotificationIndex.values().firstOrNull { + // Get the actions for the index + val standardAction = getStandardAction(this.ruleId, it) ?: return@firstOrNull false + val indexActions = standardAction.actions ?: listOf() + // Check if the input rule matches a rule generated from the static rule definitions + val targetRule = this.copy(enabled = standardAction != StandardActions.Disabled, actions = indexActions.toJson()) + ruleMatches(this, targetRule) + } /** * A check to determine if two push rules should be considered a match. @@ -46,9 +47,9 @@ private fun ruleMatches(rule: PushRule, targetRule: PushRule): Boolean { // Rules match if both are disabled, or if both are enabled and their highlight/sound/notify actions match up. return (!rule.enabled && !targetRule.enabled) || (rule.enabled && - targetRule.enabled && - rule.getHighlight() == targetRule.getHighlight() && - rule.getNotificationSound() == targetRule.getNotificationSound() && - rule.shouldNotify() == targetRule.shouldNotify() && - rule.shouldNotNotify() == targetRule.shouldNotNotify()) + targetRule.enabled && + rule.getHighlight() == targetRule.getHighlight() && + rule.getNotificationSound() == targetRule.getNotificationSound() && + rule.shouldNotify() == targetRule.shouldNotify() && + rule.shouldNotNotify() == targetRule.shouldNotNotify()) } diff --git a/vector/src/main/java/im/vector/app/features/settings/notifications/PushRuleDefinitions.kt b/vector/src/main/java/im/vector/app/features/settings/notifications/PushRuleDefinitions.kt index d101d86aa3..1507528bd1 100644 --- a/vector/src/main/java/im/vector/app/features/settings/notifications/PushRuleDefinitions.kt +++ b/vector/src/main/java/im/vector/app/features/settings/notifications/PushRuleDefinitions.kt @@ -16,7 +16,7 @@ package im.vector.app.features.settings.notifications -import org.matrix.android.sdk.api.pushrules.RuleIds +import org.matrix.android.sdk.api.session.pushrules.RuleIds fun getStandardAction(ruleId: String, index: NotificationIndex): StandardActions? { return when (ruleId) { diff --git a/vector/src/main/java/im/vector/app/features/settings/notifications/StandardActions.kt b/vector/src/main/java/im/vector/app/features/settings/notifications/StandardActions.kt index d6b6165bd9..b19b5bcdd6 100644 --- a/vector/src/main/java/im/vector/app/features/settings/notifications/StandardActions.kt +++ b/vector/src/main/java/im/vector/app/features/settings/notifications/StandardActions.kt @@ -16,7 +16,7 @@ package im.vector.app.features.settings.notifications -import org.matrix.android.sdk.api.pushrules.Action +import org.matrix.android.sdk.api.session.pushrules.Action sealed class StandardActions( val actions: List? diff --git a/vector/src/main/java/im/vector/app/features/settings/notifications/VectorSettingsAdvancedNotificationPreferenceFragment.kt b/vector/src/main/java/im/vector/app/features/settings/notifications/VectorSettingsAdvancedNotificationPreferenceFragment.kt index 3b4aef929d..a11bc9b0cf 100644 --- a/vector/src/main/java/im/vector/app/features/settings/notifications/VectorSettingsAdvancedNotificationPreferenceFragment.kt +++ b/vector/src/main/java/im/vector/app/features/settings/notifications/VectorSettingsAdvancedNotificationPreferenceFragment.kt @@ -23,12 +23,12 @@ import im.vector.app.core.preference.VectorPreference import im.vector.app.core.utils.toast import im.vector.app.features.settings.VectorSettingsBaseFragment import kotlinx.coroutines.launch -import org.matrix.android.sdk.api.pushrules.RuleIds -import org.matrix.android.sdk.api.pushrules.rest.PushRuleAndKind +import org.matrix.android.sdk.api.session.pushrules.RuleIds +import org.matrix.android.sdk.api.session.pushrules.rest.PushRuleAndKind import javax.inject.Inject class VectorSettingsAdvancedNotificationPreferenceFragment @Inject constructor() : - VectorSettingsBaseFragment() { + VectorSettingsBaseFragment() { override var titleRes: Int = R.string.settings_notification_advanced @@ -38,7 +38,7 @@ class VectorSettingsAdvancedNotificationPreferenceFragment @Inject constructor() for (preferenceKey in prefKeyToPushRuleId.keys) { val preference = findPreference(preferenceKey) if (preference is PushRulePreference) { - val ruleAndKind: PushRuleAndKind? = session.getPushRules().findDefaultRule(prefKeyToPushRuleId[preferenceKey]) + val ruleAndKind: PushRuleAndKind? = session.pushRuleService().getPushRules().findDefaultRule(prefKeyToPushRuleId[preferenceKey]) if (ruleAndKind == null) { // The rule is not defined, hide the preference @@ -57,7 +57,7 @@ class VectorSettingsAdvancedNotificationPreferenceFragment @Inject constructor() lifecycleScope.launch { val result = runCatching { - session.updatePushRuleActions(ruleAndKind.kind, + session.pushRuleService().updatePushRuleActions(ruleAndKind.kind, ruleAndKind.pushRule.ruleId, enabled, newActions) diff --git a/vector/src/main/java/im/vector/app/features/settings/notifications/VectorSettingsDefaultNotificationPreferenceFragment.kt b/vector/src/main/java/im/vector/app/features/settings/notifications/VectorSettingsDefaultNotificationPreferenceFragment.kt index 2179e0eee2..33ed7730eb 100644 --- a/vector/src/main/java/im/vector/app/features/settings/notifications/VectorSettingsDefaultNotificationPreferenceFragment.kt +++ b/vector/src/main/java/im/vector/app/features/settings/notifications/VectorSettingsDefaultNotificationPreferenceFragment.kt @@ -20,21 +20,21 @@ import android.os.Bundle import im.vector.app.R import im.vector.app.core.preference.VectorPreferenceCategory import im.vector.app.features.analytics.plan.MobileScreen -import org.matrix.android.sdk.api.pushrules.RuleIds +import org.matrix.android.sdk.api.session.pushrules.RuleIds class VectorSettingsDefaultNotificationPreferenceFragment : - VectorSettingsPushRuleNotificationPreferenceFragment() { + VectorSettingsPushRuleNotificationPreferenceFragment() { override var titleRes: Int = R.string.settings_notification_default override val preferenceXmlRes = R.xml.vector_settings_notification_default override val prefKeyToPushRuleId = mapOf( - "SETTINGS_PUSH_RULE_MESSAGES_IN_ONE_TO_ONE_PREFERENCE_KEY" to RuleIds.RULE_ID_ONE_TO_ONE_ROOM, - "SETTINGS_PUSH_RULE_MESSAGES_IN_GROUP_CHAT_PREFERENCE_KEY" to RuleIds.RULE_ID_ALL_OTHER_MESSAGES_ROOMS, - "SETTINGS_PUSH_RULE_MESSAGES_IN_E2E_ONE_ONE_CHAT_PREFERENCE_KEY" to RuleIds.RULE_ID_ONE_TO_ONE_ENCRYPTED_ROOM, - "SETTINGS_PUSH_RULE_MESSAGES_IN_E2E_GROUP_CHAT_PREFERENCE_KEY" to RuleIds.RULE_ID_ENCRYPTED - ) + "SETTINGS_PUSH_RULE_MESSAGES_IN_ONE_TO_ONE_PREFERENCE_KEY" to RuleIds.RULE_ID_ONE_TO_ONE_ROOM, + "SETTINGS_PUSH_RULE_MESSAGES_IN_GROUP_CHAT_PREFERENCE_KEY" to RuleIds.RULE_ID_ALL_OTHER_MESSAGES_ROOMS, + "SETTINGS_PUSH_RULE_MESSAGES_IN_E2E_ONE_ONE_CHAT_PREFERENCE_KEY" to RuleIds.RULE_ID_ONE_TO_ONE_ENCRYPTED_ROOM, + "SETTINGS_PUSH_RULE_MESSAGES_IN_E2E_GROUP_CHAT_PREFERENCE_KEY" to RuleIds.RULE_ID_ENCRYPTED + ) override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) diff --git a/vector/src/main/java/im/vector/app/features/settings/notifications/VectorSettingsKeywordAndMentionsNotificationPreferenceFragment.kt b/vector/src/main/java/im/vector/app/features/settings/notifications/VectorSettingsKeywordAndMentionsNotificationPreferenceFragment.kt index 731f4b3228..fcad0820cc 100644 --- a/vector/src/main/java/im/vector/app/features/settings/notifications/VectorSettingsKeywordAndMentionsNotificationPreferenceFragment.kt +++ b/vector/src/main/java/im/vector/app/features/settings/notifications/VectorSettingsKeywordAndMentionsNotificationPreferenceFragment.kt @@ -29,10 +29,10 @@ import im.vector.app.features.analytics.plan.MobileScreen import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.launch import kotlinx.coroutines.withContext -import org.matrix.android.sdk.api.pushrules.RuleIds -import org.matrix.android.sdk.api.pushrules.RuleKind -import org.matrix.android.sdk.api.pushrules.rest.PushRule -import org.matrix.android.sdk.api.pushrules.toJson +import org.matrix.android.sdk.api.session.pushrules.RuleIds +import org.matrix.android.sdk.api.session.pushrules.RuleKind +import org.matrix.android.sdk.api.session.pushrules.rest.PushRule +import org.matrix.android.sdk.api.session.pushrules.toJson class VectorSettingsKeywordAndMentionsNotificationPreferenceFragment : VectorSettingsPushRuleNotificationPreferenceFragment() { @@ -50,7 +50,7 @@ class VectorSettingsKeywordAndMentionsNotificationPreferenceFragment : override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) - session.getKeywords().observe(viewLifecycleOwner, this::updateWithKeywords) + session.pushRuleService().getKeywords().observe(viewLifecycleOwner, this::updateWithKeywords) } override fun bindPref() { @@ -61,7 +61,7 @@ class VectorSettingsKeywordAndMentionsNotificationPreferenceFragment : val yourKeywordsCategory = findPreference("SETTINGS_YOUR_KEYWORDS")!! yourKeywordsCategory.isIconSpaceReserved = false - val keywordRules = session.getPushRules().content?.filter { !it.ruleId.startsWith(".") }.orEmpty() + val keywordRules = session.pushRuleService().getPushRules().content?.filter { !it.ruleId.startsWith(".") }.orEmpty() val enableKeywords = keywordRules.isEmpty() || keywordRules.any(PushRule::enabled) val editKeywordPreference = findPreference("SETTINGS_KEYWORD_EDIT")!! @@ -119,7 +119,7 @@ class VectorSettingsKeywordAndMentionsNotificationPreferenceFragment : val results = keywords.map { runCatching { withContext(Dispatchers.Default) { - session.updatePushRuleActions(RuleKind.CONTENT, + session.pushRuleService().updatePushRuleActions(RuleKind.CONTENT, it, enabled, newActions) @@ -151,7 +151,7 @@ class VectorSettingsKeywordAndMentionsNotificationPreferenceFragment : displayLoadingView() lifecycleScope.launch { val result = runCatching { - session.addPushRule(RuleKind.CONTENT, newRule) + session.pushRuleService().addPushRule(RuleKind.CONTENT, newRule) } hideLoadingView() if (!isAdded) { @@ -167,7 +167,7 @@ class VectorSettingsKeywordAndMentionsNotificationPreferenceFragment : displayLoadingView() lifecycleScope.launch { val result = runCatching { - session.removePushRule(RuleKind.CONTENT, keyword) + session.pushRuleService().removePushRule(RuleKind.CONTENT, keyword) } hideLoadingView() if (!isAdded) { diff --git a/vector/src/main/java/im/vector/app/features/settings/notifications/VectorSettingsNotificationPreferenceFragment.kt b/vector/src/main/java/im/vector/app/features/settings/notifications/VectorSettingsNotificationPreferenceFragment.kt index 4199bd1753..d9cd5b3461 100644 --- a/vector/src/main/java/im/vector/app/features/settings/notifications/VectorSettingsNotificationPreferenceFragment.kt +++ b/vector/src/main/java/im/vector/app/features/settings/notifications/VectorSettingsNotificationPreferenceFragment.kt @@ -21,6 +21,7 @@ import android.content.Context import android.content.Intent import android.media.RingtoneManager import android.net.Uri +import android.os.Bundle import android.os.Parcelable import android.widget.Toast import androidx.lifecycle.LiveData @@ -38,8 +39,10 @@ import im.vector.app.core.preference.VectorPreferenceCategory import im.vector.app.core.preference.VectorSwitchPreference import im.vector.app.core.pushers.PushersManager import im.vector.app.core.services.GuardServiceStarter +import im.vector.app.core.utils.combineLatest import im.vector.app.core.utils.isIgnoringBatteryOptimizations import im.vector.app.core.utils.requestDisablingBatteryOptimization +import im.vector.app.features.analytics.plan.MobileScreen import im.vector.app.features.notifications.NotificationUtils import im.vector.app.features.settings.BackgroundSyncMode import im.vector.app.features.settings.BackgroundSyncModeChooserDialog @@ -50,12 +53,11 @@ import im.vector.app.push.fcm.FcmHelper import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.launch import org.matrix.android.sdk.api.extensions.tryOrNull -import org.matrix.android.sdk.api.pushrules.RuleIds -import org.matrix.android.sdk.api.pushrules.RuleKind import org.matrix.android.sdk.api.session.Session import org.matrix.android.sdk.api.session.identity.ThreePid import org.matrix.android.sdk.api.session.pushers.Pusher -import org.matrix.android.sdk.internal.extensions.combineLatest +import org.matrix.android.sdk.api.session.pushrules.RuleIds +import org.matrix.android.sdk.api.session.pushrules.RuleKind import javax.inject.Inject // Referenced in vector_settings_preferences_root.xml @@ -72,9 +74,14 @@ class VectorSettingsNotificationPreferenceFragment @Inject constructor( private var interactionListener: VectorSettingsFragmentInteractionListener? = null + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + analyticsScreenName = MobileScreen.ScreenName.SettingsNotifications + } + override fun bindPref() { findPreference(VectorPreferences.SETTINGS_ENABLE_ALL_NOTIF_PREFERENCE_KEY)!!.let { pref -> - val pushRuleService = session + val pushRuleService = session.pushRuleService() val mRuleMaster = pushRuleService.getPushRules().getAllRules() .find { it.ruleId == RuleIds.RULE_ID_DISABLE_ALL } @@ -97,7 +104,7 @@ class VectorSettingsNotificationPreferenceFragment @Inject constructor( } else { FcmHelper.getFcmToken(requireContext())?.let { pushManager.unregisterPusher(it) - session.refreshPushers() + session.pushersService().refreshPushers() } } } @@ -311,7 +318,7 @@ class VectorSettingsNotificationPreferenceFragment @Inject constructor( override fun onResume() { super.onResume() - activeSessionHolder.getSafeActiveSession()?.refreshPushers() + activeSessionHolder.getSafeActiveSession()?.pushersService()?.refreshPushers() interactionListener?.requestedKeyToHighlight()?.let { key -> interactionListener?.requestHighlightPreferenceKeyOnResume(null) @@ -358,7 +365,7 @@ class VectorSettingsNotificationPreferenceFragment @Inject constructor( } private fun updateEnabledForAccount(preference: Preference?) { - val pushRuleService = session + val pushRuleService = session.pushRuleService() val switchPref = preference as SwitchPreference pushRuleService.getPushRules().getAllRules() .find { it.ruleId == RuleIds.RULE_ID_DISABLE_ALL } @@ -407,15 +414,15 @@ private fun SwitchPreference.setTransactionalSwitchChangeListener(scope: Corouti * @see ThreePid.Email */ private fun Session.getEmailsWithPushInformation(): List> { - val emailPushers = getPushers().filter { it.kind == Pusher.KIND_EMAIL } - return getThreePids() + val emailPushers = pushersService().getPushers().filter { it.kind == Pusher.KIND_EMAIL } + return profileService().getThreePids() .filterIsInstance() .map { it to emailPushers.any { pusher -> pusher.pushKey == it.email } } } private fun Session.getEmailsWithPushInformationLive(): LiveData>> { - val emailThreePids = getThreePidsLive(refreshData = true).map { it.filterIsInstance() } - val emailPushers = getPushersLive().map { it.filter { pusher -> pusher.kind == Pusher.KIND_EMAIL } } + val emailThreePids = profileService().getThreePidsLive(refreshData = true).map { it.filterIsInstance() } + val emailPushers = pushersService().getPushersLive().map { it.filter { pusher -> pusher.kind == Pusher.KIND_EMAIL } } return combineLatest(emailThreePids, emailPushers) { emailThreePidsResult, emailPushersResult -> emailThreePidsResult.map { it to emailPushersResult.any { pusher -> pusher.pushKey == it.email } } }.distinctUntilChanged() diff --git a/vector/src/main/java/im/vector/app/features/settings/notifications/VectorSettingsOtherNotificationPreferenceFragment.kt b/vector/src/main/java/im/vector/app/features/settings/notifications/VectorSettingsOtherNotificationPreferenceFragment.kt index 71f8f0920a..3638fb8526 100644 --- a/vector/src/main/java/im/vector/app/features/settings/notifications/VectorSettingsOtherNotificationPreferenceFragment.kt +++ b/vector/src/main/java/im/vector/app/features/settings/notifications/VectorSettingsOtherNotificationPreferenceFragment.kt @@ -18,21 +18,21 @@ package im.vector.app.features.settings.notifications import im.vector.app.R import im.vector.app.core.preference.VectorPreferenceCategory -import org.matrix.android.sdk.api.pushrules.RuleIds +import org.matrix.android.sdk.api.session.pushrules.RuleIds class VectorSettingsOtherNotificationPreferenceFragment : - VectorSettingsPushRuleNotificationPreferenceFragment() { + VectorSettingsPushRuleNotificationPreferenceFragment() { override var titleRes: Int = R.string.settings_notification_other override val preferenceXmlRes = R.xml.vector_settings_notification_other override val prefKeyToPushRuleId = mapOf( - "SETTINGS_PUSH_RULE_INVITED_TO_ROOM_PREFERENCE_KEY" to RuleIds.RULE_ID_INVITE_ME, - "SETTINGS_PUSH_RULE_CALL_INVITATIONS_PREFERENCE_KEY" to RuleIds.RULE_ID_CALL, - "SETTINGS_PUSH_RULE_MESSAGES_SENT_BY_BOT_PREFERENCE_KEY" to RuleIds.RULE_ID_SUPPRESS_BOTS_NOTIFICATIONS, - "SETTINGS_PUSH_RULE_ROOMS_UPGRADED_KEY" to RuleIds.RULE_ID_TOMBSTONE - ) + "SETTINGS_PUSH_RULE_INVITED_TO_ROOM_PREFERENCE_KEY" to RuleIds.RULE_ID_INVITE_ME, + "SETTINGS_PUSH_RULE_CALL_INVITATIONS_PREFERENCE_KEY" to RuleIds.RULE_ID_CALL, + "SETTINGS_PUSH_RULE_MESSAGES_SENT_BY_BOT_PREFERENCE_KEY" to RuleIds.RULE_ID_SUPPRESS_BOTS_NOTIFICATIONS, + "SETTINGS_PUSH_RULE_ROOMS_UPGRADED_KEY" to RuleIds.RULE_ID_TOMBSTONE + ) override fun bindPref() { super.bindPref() diff --git a/vector/src/main/java/im/vector/app/features/settings/notifications/VectorSettingsPushRuleNotificationPreferenceFragment.kt b/vector/src/main/java/im/vector/app/features/settings/notifications/VectorSettingsPushRuleNotificationPreferenceFragment.kt index 26ee2fc601..1381dd79ae 100644 --- a/vector/src/main/java/im/vector/app/features/settings/notifications/VectorSettingsPushRuleNotificationPreferenceFragment.kt +++ b/vector/src/main/java/im/vector/app/features/settings/notifications/VectorSettingsPushRuleNotificationPreferenceFragment.kt @@ -21,11 +21,11 @@ import androidx.preference.Preference import im.vector.app.core.preference.VectorCheckboxPreference import im.vector.app.features.settings.VectorSettingsBaseFragment import kotlinx.coroutines.launch -import org.matrix.android.sdk.api.pushrules.RuleKind -import org.matrix.android.sdk.api.pushrules.rest.PushRuleAndKind +import org.matrix.android.sdk.api.session.pushrules.RuleKind +import org.matrix.android.sdk.api.session.pushrules.rest.PushRuleAndKind abstract class VectorSettingsPushRuleNotificationPreferenceFragment : - VectorSettingsBaseFragment() { + VectorSettingsBaseFragment() { abstract val prefKeyToPushRuleId: Map @@ -33,7 +33,7 @@ abstract class VectorSettingsPushRuleNotificationPreferenceFragment : for (preferenceKey in prefKeyToPushRuleId.keys) { val preference = findPreference(preferenceKey)!! preference.isIconSpaceReserved = false - val ruleAndKind: PushRuleAndKind? = session.getPushRules().findDefaultRule(prefKeyToPushRuleId[preferenceKey]) + val ruleAndKind: PushRuleAndKind? = session.pushRuleService().getPushRules().findDefaultRule(prefKeyToPushRuleId[preferenceKey]) if (ruleAndKind == null) { // The rule is not defined, hide the preference preference.isVisible = false @@ -58,7 +58,7 @@ abstract class VectorSettingsPushRuleNotificationPreferenceFragment : lifecycleScope.launch { val result = runCatching { - session.updatePushRuleActions(kind, + session.pushRuleService().updatePushRuleActions(kind, ruleId, enabled, newActions) diff --git a/vector/src/main/java/im/vector/app/features/settings/push/PushGatewaysFragment.kt b/vector/src/main/java/im/vector/app/features/settings/push/PushGatewaysFragment.kt index 65c62542bb..73a74b1e3f 100644 --- a/vector/src/main/java/im/vector/app/features/settings/push/PushGatewaysFragment.kt +++ b/vector/src/main/java/im/vector/app/features/settings/push/PushGatewaysFragment.kt @@ -28,7 +28,6 @@ import com.google.android.material.dialog.MaterialAlertDialogBuilder import im.vector.app.R import im.vector.app.core.extensions.cleanup import im.vector.app.core.extensions.configureWith -import im.vector.app.core.extensions.exhaustive import im.vector.app.core.platform.VectorBaseFragment import im.vector.app.databinding.FragmentGenericRecyclerBinding import org.matrix.android.sdk.api.session.pushers.Pusher @@ -78,7 +77,7 @@ class PushGatewaysFragment @Inject constructor( .setPositiveButton(android.R.string.ok, null) .show() } - }.exhaustive + } } } diff --git a/vector/src/main/java/im/vector/app/features/settings/push/PushGatewaysViewModel.kt b/vector/src/main/java/im/vector/app/features/settings/push/PushGatewaysViewModel.kt index 1256673364..14b7a53b65 100644 --- a/vector/src/main/java/im/vector/app/features/settings/push/PushGatewaysViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/settings/push/PushGatewaysViewModel.kt @@ -25,7 +25,6 @@ import dagger.assisted.AssistedFactory import dagger.assisted.AssistedInject import im.vector.app.core.di.MavericksAssistedViewModelFactory import im.vector.app.core.di.hiltMavericksViewModelFactory -import im.vector.app.core.extensions.exhaustive import im.vector.app.core.platform.VectorViewModel import kotlinx.coroutines.launch import org.matrix.android.sdk.api.session.Session @@ -50,7 +49,7 @@ class PushGatewaysViewModel @AssistedInject constructor(@Assisted initialState: init { observePushers() // Force a refresh - session.refreshPushers() + session.pushersService().refreshPushers() } private fun observePushers() { @@ -65,13 +64,13 @@ class PushGatewaysViewModel @AssistedInject constructor(@Assisted initialState: when (action) { is PushGatewayAction.Refresh -> handleRefresh() is PushGatewayAction.RemovePusher -> removePusher(action.pusher) - }.exhaustive + } } private fun removePusher(pusher: Pusher) { viewModelScope.launch { kotlin.runCatching { - session.removePusher(pusher) + session.pushersService().removePusher(pusher) }.onFailure { _viewEvents.post(PushGatewayViewEvents.RemovePusherFailed(it)) } @@ -79,6 +78,6 @@ class PushGatewaysViewModel @AssistedInject constructor(@Assisted initialState: } private fun handleRefresh() { - session.refreshPushers() + session.pushersService().refreshPushers() } } diff --git a/vector/src/main/java/im/vector/app/features/settings/push/PushRuleItem.kt b/vector/src/main/java/im/vector/app/features/settings/push/PushRuleItem.kt index f6e03143f5..172764d87f 100644 --- a/vector/src/main/java/im/vector/app/features/settings/push/PushRuleItem.kt +++ b/vector/src/main/java/im/vector/app/features/settings/push/PushRuleItem.kt @@ -30,8 +30,8 @@ import im.vector.app.R import im.vector.app.core.epoxy.VectorEpoxyHolder import im.vector.app.features.notifications.toNotificationAction import im.vector.app.features.themes.ThemeUtils -import org.matrix.android.sdk.api.pushrules.getActions -import org.matrix.android.sdk.api.pushrules.rest.PushRule +import org.matrix.android.sdk.api.session.pushrules.getActions +import org.matrix.android.sdk.api.session.pushrules.rest.PushRule @EpoxyModelClass(layout = R.layout.item_pushrule_raw) abstract class PushRuleItem : EpoxyModelWithHolder() { diff --git a/vector/src/main/java/im/vector/app/features/settings/push/PushRulesViewModel.kt b/vector/src/main/java/im/vector/app/features/settings/push/PushRulesViewModel.kt index 0b6b72bb10..deda34115e 100644 --- a/vector/src/main/java/im/vector/app/features/settings/push/PushRulesViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/settings/push/PushRulesViewModel.kt @@ -23,20 +23,20 @@ import im.vector.app.core.di.SingletonEntryPoint import im.vector.app.core.platform.EmptyAction import im.vector.app.core.platform.EmptyViewEvents import im.vector.app.core.platform.VectorViewModel -import org.matrix.android.sdk.api.pushrules.rest.PushRule +import org.matrix.android.sdk.api.session.pushrules.rest.PushRule data class PushRulesViewState( val rules: List = emptyList() ) : MavericksState class PushRulesViewModel(initialState: PushRulesViewState) : - VectorViewModel(initialState) { + VectorViewModel(initialState) { companion object : MavericksViewModelFactory { override fun initialState(viewModelContext: ViewModelContext): PushRulesViewState? { val session = EntryPoints.get(viewModelContext.app(), SingletonEntryPoint::class.java).activeSessionHolder().getActiveSession() - val rules = session.getPushRules().getAllRules() + val rules = session.pushRuleService().getPushRules().getAllRules() return PushRulesViewState(rules) } } diff --git a/vector/src/main/java/im/vector/app/features/settings/threepids/ThreePidsSettingsController.kt b/vector/src/main/java/im/vector/app/features/settings/threepids/ThreePidsSettingsController.kt index d374357396..7a4033fb82 100644 --- a/vector/src/main/java/im/vector/app/features/settings/threepids/ThreePidsSettingsController.kt +++ b/vector/src/main/java/im/vector/app/features/settings/threepids/ThreePidsSettingsController.kt @@ -25,7 +25,6 @@ import im.vector.app.R import im.vector.app.core.epoxy.loadingItem import im.vector.app.core.epoxy.noResultItem import im.vector.app.core.error.ErrorFormatter -import im.vector.app.core.extensions.exhaustive import im.vector.app.core.extensions.getFormattedValue import im.vector.app.core.resources.ColorProvider import im.vector.app.core.resources.StringProvider @@ -94,6 +93,7 @@ class ThreePidsSettingsController @Inject constructor( val dataList = data.threePids.invoke() buildThreePids(dataList, data) } + else -> Unit } } @@ -160,7 +160,7 @@ class ThreePidsSettingsController @Inject constructor( } } is ThreePidsSettingsUiState.AddingPhoneNumber -> Unit - }.exhaustive + } settingsSectionTitleItem { id("msisdn") @@ -223,7 +223,7 @@ class ThreePidsSettingsController @Inject constructor( cancelOnClick { host.interactionListener?.cancelAdding() } } } - }.exhaustive + } } private fun buildThreePid(idPrefix: String, threePid: ThreePid) { diff --git a/vector/src/main/java/im/vector/app/features/settings/threepids/ThreePidsSettingsFragment.kt b/vector/src/main/java/im/vector/app/features/settings/threepids/ThreePidsSettingsFragment.kt index bdb1fb895f..2a9db78121 100644 --- a/vector/src/main/java/im/vector/app/features/settings/threepids/ThreePidsSettingsFragment.kt +++ b/vector/src/main/java/im/vector/app/features/settings/threepids/ThreePidsSettingsFragment.kt @@ -28,7 +28,6 @@ import com.google.android.material.dialog.MaterialAlertDialogBuilder import im.vector.app.R import im.vector.app.core.extensions.cleanup import im.vector.app.core.extensions.configureWith -import im.vector.app.core.extensions.exhaustive import im.vector.app.core.extensions.getFormattedValue import im.vector.app.core.extensions.hideKeyboard import im.vector.app.core.extensions.isEmail @@ -62,9 +61,9 @@ class ThreePidsSettingsFragment @Inject constructor( viewModel.observeViewEvents { when (it) { - is ThreePidsSettingsViewEvents.Failure -> displayErrorDialog(it.throwable) + is ThreePidsSettingsViewEvents.Failure -> displayErrorDialog(it.throwable) is ThreePidsSettingsViewEvents.RequestReAuth -> askAuthentication(it) - }.exhaustive + } } } @@ -76,10 +75,11 @@ class ThreePidsSettingsFragment @Inject constructor( reAuthActivityResultLauncher.launch(intent) } } + private val reAuthActivityResultLauncher = registerStartForActivityResult { activityResult -> if (activityResult.resultCode == Activity.RESULT_OK) { when (activityResult.data?.extras?.getString(ReAuthActivity.RESULT_FLOW_TYPE)) { - LoginFlowTypes.SSO -> { + LoginFlowTypes.SSO -> { viewModel.handle(ThreePidsSettingsAction.SsoAuthDone) } LoginFlowTypes.PASSWORD -> { diff --git a/vector/src/main/java/im/vector/app/features/settings/threepids/ThreePidsSettingsViewEvents.kt b/vector/src/main/java/im/vector/app/features/settings/threepids/ThreePidsSettingsViewEvents.kt index 94ea8d24c6..9768d7bd93 100644 --- a/vector/src/main/java/im/vector/app/features/settings/threepids/ThreePidsSettingsViewEvents.kt +++ b/vector/src/main/java/im/vector/app/features/settings/threepids/ThreePidsSettingsViewEvents.kt @@ -22,6 +22,6 @@ import org.matrix.android.sdk.api.auth.registration.RegistrationFlowResponse sealed class ThreePidsSettingsViewEvents : VectorViewEvents { data class Failure(val throwable: Throwable) : ThreePidsSettingsViewEvents() -// object RequestPassword : ThreePidsSettingsViewEvents() + // object RequestPassword : ThreePidsSettingsViewEvents() data class RequestReAuth(val registrationFlowResponse: RegistrationFlowResponse, val lastErrorCode: String?) : ThreePidsSettingsViewEvents() } diff --git a/vector/src/main/java/im/vector/app/features/settings/threepids/ThreePidsSettingsViewModel.kt b/vector/src/main/java/im/vector/app/features/settings/threepids/ThreePidsSettingsViewModel.kt index 12ff436ccb..587337d210 100644 --- a/vector/src/main/java/im/vector/app/features/settings/threepids/ThreePidsSettingsViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/settings/threepids/ThreePidsSettingsViewModel.kt @@ -25,7 +25,6 @@ import dagger.assisted.AssistedInject import im.vector.app.R import im.vector.app.core.di.MavericksAssistedViewModelFactory import im.vector.app.core.di.hiltMavericksViewModelFactory -import im.vector.app.core.extensions.exhaustive import im.vector.app.core.platform.VectorViewModel import im.vector.app.core.resources.StringProvider import im.vector.app.core.utils.ReadOnceTrue @@ -37,9 +36,9 @@ import org.matrix.android.sdk.api.auth.UserPasswordAuth import org.matrix.android.sdk.api.auth.registration.RegistrationFlowResponse import org.matrix.android.sdk.api.session.Session import org.matrix.android.sdk.api.session.identity.ThreePid +import org.matrix.android.sdk.api.session.uia.DefaultBaseAuth +import org.matrix.android.sdk.api.util.fromBase64 import org.matrix.android.sdk.flow.flow -import org.matrix.android.sdk.internal.crypto.crosssigning.fromBase64 -import org.matrix.android.sdk.internal.crypto.model.rest.DefaultBaseAuth import timber.log.Timber import kotlin.coroutines.Continuation import kotlin.coroutines.resume @@ -134,7 +133,8 @@ class ThreePidsSettingsViewModel @AssistedInject constructor( } } is ThreePidsSettingsAction.PasswordAuthDone -> { - val decryptedPass = session.loadSecureSecret(action.password.fromBase64().inputStream(), ReAuthActivity.DEFAULT_RESULT_KEYSTORE_ALIAS) + val decryptedPass = session.secureStorageService() + .loadSecureSecret(action.password.fromBase64().inputStream(), ReAuthActivity.DEFAULT_RESULT_KEYSTORE_ALIAS) uiaContinuation?.resume( UserPasswordAuth( session = pendingAuth?.session, @@ -149,7 +149,7 @@ class ThreePidsSettingsViewModel @AssistedInject constructor( uiaContinuation = null pendingAuth = null } - }.exhaustive + } } var uiaContinuation: Continuation? = null @@ -176,7 +176,7 @@ class ThreePidsSettingsViewModel @AssistedInject constructor( viewModelScope.launch { // First submit the code try { - session.submitSmsCode(action.threePid, action.code) + session.profileService().submitSmsCode(action.threePid, action.code) } catch (failure: Throwable) { isLoading(false) setState { @@ -191,7 +191,7 @@ class ThreePidsSettingsViewModel @AssistedInject constructor( // then finalize pendingThreePid = action.threePid - loadingSuspendable { session.finalizeAddingThreePid(action.threePid, uiaInterceptor) } + loadingSuspendable { session.profileService().finalizeAddingThreePid(action.threePid, uiaInterceptor) } } } @@ -219,7 +219,7 @@ class ThreePidsSettingsViewModel @AssistedInject constructor( } else { viewModelScope.launch { loadingSuspendable { - session.addThreePid(action.threePid) + session.profileService().addThreePid(action.threePid) // Also reset the state setState { copy( @@ -236,14 +236,14 @@ class ThreePidsSettingsViewModel @AssistedInject constructor( isLoading(true) pendingThreePid = action.threePid viewModelScope.launch { - loadingSuspendable { session.finalizeAddingThreePid(action.threePid, uiaInterceptor) } + loadingSuspendable { session.profileService().finalizeAddingThreePid(action.threePid, uiaInterceptor) } } } private fun handleCancelThreePid(action: ThreePidsSettingsAction.CancelThreePid) { isLoading(true) viewModelScope.launch { - loadingSuspendable { session.cancelAddingThreePid(action.threePid) } + loadingSuspendable { session.profileService().cancelAddingThreePid(action.threePid) } } } @@ -259,7 +259,7 @@ class ThreePidsSettingsViewModel @AssistedInject constructor( private fun handleDeleteThreePid(action: ThreePidsSettingsAction.DeleteThreePid) { isLoading(true) viewModelScope.launch { - loadingSuspendable { session.deleteThreePid(action.threePid) } + loadingSuspendable { session.profileService().deleteThreePid(action.threePid) } } } } diff --git a/vector/src/main/java/im/vector/app/features/settings/troubleshoot/NotificationTroubleshootRecyclerViewAdapter.kt b/vector/src/main/java/im/vector/app/features/settings/troubleshoot/NotificationTroubleshootRecyclerViewAdapter.kt index 8f0b2cfe74..8f762a9abc 100644 --- a/vector/src/main/java/im/vector/app/features/settings/troubleshoot/NotificationTroubleshootRecyclerViewAdapter.kt +++ b/vector/src/main/java/im/vector/app/features/settings/troubleshoot/NotificationTroubleshootRecyclerViewAdapter.kt @@ -25,7 +25,7 @@ import im.vector.app.databinding.ItemNotificationTroubleshootBinding import im.vector.app.features.themes.ThemeUtils class NotificationTroubleshootRecyclerViewAdapter(val tests: ArrayList) : - RecyclerView.Adapter() { + RecyclerView.Adapter() { override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder { val inflater = LayoutInflater.from(parent.context) diff --git a/vector/src/main/java/im/vector/app/features/settings/troubleshoot/TestAccountSettings.kt b/vector/src/main/java/im/vector/app/features/settings/troubleshoot/TestAccountSettings.kt index 9d06e1724c..b6e78fa580 100644 --- a/vector/src/main/java/im/vector/app/features/settings/troubleshoot/TestAccountSettings.kt +++ b/vector/src/main/java/im/vector/app/features/settings/troubleshoot/TestAccountSettings.kt @@ -25,8 +25,8 @@ import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.launch import kotlinx.coroutines.withContext import org.matrix.android.sdk.api.extensions.tryOrNull -import org.matrix.android.sdk.api.pushrules.RuleIds -import org.matrix.android.sdk.api.pushrules.RuleKind +import org.matrix.android.sdk.api.session.pushrules.RuleIds +import org.matrix.android.sdk.api.session.pushrules.RuleKind import javax.inject.Inject /** @@ -34,11 +34,11 @@ import javax.inject.Inject */ class TestAccountSettings @Inject constructor(private val stringProvider: StringProvider, private val activeSessionHolder: ActiveSessionHolder) : - TroubleshootTest(R.string.settings_troubleshoot_test_account_settings_title) { + TroubleshootTest(R.string.settings_troubleshoot_test_account_settings_title) { override fun perform(activityResultLauncher: ActivityResultLauncher) { val session = activeSessionHolder.getSafeActiveSession() ?: return - val defaultRule = session.getPushRules().getAllRules() + val defaultRule = session.pushRuleService().getPushRules().getAllRules() .find { it.ruleId == RuleIds.RULE_ID_DISABLE_ALL } if (defaultRule != null) { @@ -54,7 +54,7 @@ class TestAccountSettings @Inject constructor(private val stringProvider: String session.coroutineScope.launch { tryOrNull { - session.updatePushRuleEnableStatus(RuleKind.OVERRIDE, defaultRule, !defaultRule.enabled) + session.pushRuleService().updatePushRuleEnableStatus(RuleKind.OVERRIDE, defaultRule, !defaultRule.enabled) } withContext(Dispatchers.Main) { manager?.retry(activityResultLauncher) diff --git a/vector/src/main/java/im/vector/app/features/settings/troubleshoot/TestDeviceSettings.kt b/vector/src/main/java/im/vector/app/features/settings/troubleshoot/TestDeviceSettings.kt index f9fc7e80e3..c58b7f4ebc 100644 --- a/vector/src/main/java/im/vector/app/features/settings/troubleshoot/TestDeviceSettings.kt +++ b/vector/src/main/java/im/vector/app/features/settings/troubleshoot/TestDeviceSettings.kt @@ -27,7 +27,7 @@ import javax.inject.Inject */ class TestDeviceSettings @Inject constructor(private val vectorPreferences: VectorPreferences, private val stringProvider: StringProvider) : - TroubleshootTest(R.string.settings_troubleshoot_test_device_settings_title) { + TroubleshootTest(R.string.settings_troubleshoot_test_device_settings_title) { override fun perform(activityResultLauncher: ActivityResultLauncher) { if (vectorPreferences.areNotificationEnabledForDevice()) { diff --git a/vector/src/main/java/im/vector/app/features/settings/troubleshoot/TestNotification.kt b/vector/src/main/java/im/vector/app/features/settings/troubleshoot/TestNotification.kt index 412916c201..1efe76205a 100644 --- a/vector/src/main/java/im/vector/app/features/settings/troubleshoot/TestNotification.kt +++ b/vector/src/main/java/im/vector/app/features/settings/troubleshoot/TestNotification.kt @@ -30,7 +30,7 @@ import javax.inject.Inject class TestNotification @Inject constructor(private val context: Context, private val notificationUtils: NotificationUtils, private val stringProvider: StringProvider) : - TroubleshootTest(R.string.settings_troubleshoot_test_notification_title) { + TroubleshootTest(R.string.settings_troubleshoot_test_notification_title) { override fun perform(activityResultLauncher: ActivityResultLauncher) { // Display the notification right now diff --git a/vector/src/main/java/im/vector/app/features/settings/troubleshoot/TestPushRulesSettings.kt b/vector/src/main/java/im/vector/app/features/settings/troubleshoot/TestPushRulesSettings.kt index 79e4377a5c..69e3021738 100644 --- a/vector/src/main/java/im/vector/app/features/settings/troubleshoot/TestPushRulesSettings.kt +++ b/vector/src/main/java/im/vector/app/features/settings/troubleshoot/TestPushRulesSettings.kt @@ -21,13 +21,13 @@ import im.vector.app.R import im.vector.app.core.di.ActiveSessionHolder import im.vector.app.core.resources.StringProvider import im.vector.app.features.notifications.toNotificationAction -import org.matrix.android.sdk.api.pushrules.RuleIds -import org.matrix.android.sdk.api.pushrules.getActions +import org.matrix.android.sdk.api.session.pushrules.RuleIds +import org.matrix.android.sdk.api.session.pushrules.getActions import javax.inject.Inject class TestPushRulesSettings @Inject constructor(private val activeSessionHolder: ActiveSessionHolder, private val stringProvider: StringProvider) : - TroubleshootTest(R.string.settings_troubleshoot_test_bing_settings_title) { + TroubleshootTest(R.string.settings_troubleshoot_test_bing_settings_title) { private val testedRules = listOf(RuleIds.RULE_ID_CONTAIN_DISPLAY_NAME, @@ -37,7 +37,7 @@ class TestPushRulesSettings @Inject constructor(private val activeSessionHolder: override fun perform(activityResultLauncher: ActivityResultLauncher) { val session = activeSessionHolder.getSafeActiveSession() ?: return - val pushRules = session.getPushRules().getAllRules() + val pushRules = session.pushRuleService().getPushRules().getAllRules() var oneOrMoreRuleIsOff = false var oneOrMoreRuleAreSilent = false testedRules.forEach { ruleId -> diff --git a/vector/src/main/java/im/vector/app/features/settings/troubleshoot/TestSystemSettings.kt b/vector/src/main/java/im/vector/app/features/settings/troubleshoot/TestSystemSettings.kt index 42f506d4a6..f3e64659cf 100644 --- a/vector/src/main/java/im/vector/app/features/settings/troubleshoot/TestSystemSettings.kt +++ b/vector/src/main/java/im/vector/app/features/settings/troubleshoot/TestSystemSettings.kt @@ -29,7 +29,7 @@ import javax.inject.Inject */ class TestSystemSettings @Inject constructor(private val context: FragmentActivity, private val stringProvider: StringProvider) : - TroubleshootTest(R.string.settings_troubleshoot_test_system_settings_title) { + TroubleshootTest(R.string.settings_troubleshoot_test_system_settings_title) { override fun perform(activityResultLauncher: ActivityResultLauncher) { if (NotificationManagerCompat.from(context).areNotificationsEnabled()) { diff --git a/vector/src/main/java/im/vector/app/features/share/IncomingShareFragment.kt b/vector/src/main/java/im/vector/app/features/share/IncomingShareFragment.kt index 62fb064536..199536b62b 100644 --- a/vector/src/main/java/im/vector/app/features/share/IncomingShareFragment.kt +++ b/vector/src/main/java/im/vector/app/features/share/IncomingShareFragment.kt @@ -34,7 +34,6 @@ import im.vector.app.R import im.vector.app.core.di.ActiveSessionHolder import im.vector.app.core.extensions.cleanup import im.vector.app.core.extensions.configureWith -import im.vector.app.core.extensions.exhaustive import im.vector.app.core.extensions.registerStartForActivityResult import im.vector.app.core.platform.VectorBaseFragment import im.vector.app.databinding.FragmentIncomingShareBinding @@ -42,6 +41,7 @@ import im.vector.app.features.attachments.AttachmentsHelper import im.vector.app.features.attachments.preview.AttachmentsPreviewActivity import im.vector.app.features.attachments.preview.AttachmentsPreviewArgs import org.matrix.android.sdk.api.session.content.ContentAttachmentData +import org.matrix.android.sdk.api.session.getRoomSummary import org.matrix.android.sdk.api.session.room.model.RoomSummary import javax.inject.Inject @@ -81,7 +81,7 @@ class IncomingShareFragment @Inject constructor( is IncomingShareViewEvents.ShareToRoom -> handleShareToRoom(it) is IncomingShareViewEvents.EditMediaBeforeSending -> handleEditMediaBeforeSending(it) is IncomingShareViewEvents.MultipleRoomsShareDone -> handleMultipleRoomsShareDone(it) - }.exhaustive + } } val intent = vectorBaseActivity.intent diff --git a/vector/src/main/java/im/vector/app/features/share/IncomingShareViewModel.kt b/vector/src/main/java/im/vector/app/features/share/IncomingShareViewModel.kt index 4a413ad8ba..2be4c99fba 100644 --- a/vector/src/main/java/im/vector/app/features/share/IncomingShareViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/share/IncomingShareViewModel.kt @@ -22,7 +22,6 @@ import dagger.assisted.AssistedFactory import dagger.assisted.AssistedInject import im.vector.app.core.di.MavericksAssistedViewModelFactory import im.vector.app.core.di.hiltMavericksViewModelFactory -import im.vector.app.core.extensions.exhaustive import im.vector.app.core.extensions.toggle import im.vector.app.core.platform.VectorViewModel import im.vector.app.features.attachments.isPreviewable @@ -35,6 +34,7 @@ import kotlinx.coroutines.flow.sample import org.matrix.android.sdk.api.query.QueryStringValue import org.matrix.android.sdk.api.session.Session import org.matrix.android.sdk.api.session.content.ContentAttachmentData +import org.matrix.android.sdk.api.session.getRoom import org.matrix.android.sdk.api.session.room.model.Membership import org.matrix.android.sdk.api.session.room.roomSummaryQueryParams import org.matrix.android.sdk.flow.flow @@ -43,7 +43,7 @@ class IncomingShareViewModel @AssistedInject constructor( @Assisted initialState: IncomingShareViewState, private val session: Session, private val breadcrumbsRoomComparator: BreadcrumbsRoomComparator) : - VectorViewModel(initialState) { + VectorViewModel(initialState) { @AssistedFactory interface Factory : MavericksAssistedViewModelFactory { @@ -96,7 +96,7 @@ class IncomingShareViewModel @AssistedInject constructor( is IncomingShareAction.ShareMedia -> handleShareMediaToSelectedRooms(action) is IncomingShareAction.FilterWith -> handleFilter(action) is IncomingShareAction.UpdateSharedData -> handleUpdateSharedData(action) - }.exhaustive + } } private fun handleUpdateSharedData(action: IncomingShareAction.UpdateSharedData) { @@ -127,7 +127,7 @@ class IncomingShareViewModel @AssistedInject constructor( is SharedData.Attachments -> { shareAttachments(sharedData.attachmentData, state.selectedRoomIds, proposeMediaEdition = true, compressMediaBeforeSending = false) } - }.exhaustive + } } } diff --git a/vector/src/main/java/im/vector/app/features/signout/soft/SoftLogoutController.kt b/vector/src/main/java/im/vector/app/features/signout/soft/SoftLogoutController.kt index 0cd9cde547..e2f3c14e7d 100644 --- a/vector/src/main/java/im/vector/app/features/signout/soft/SoftLogoutController.kt +++ b/vector/src/main/java/im/vector/app/features/signout/soft/SoftLogoutController.kt @@ -18,8 +18,9 @@ package im.vector.app.features.signout.soft import com.airbnb.epoxy.EpoxyController import com.airbnb.mvrx.Fail -import com.airbnb.mvrx.Incomplete +import com.airbnb.mvrx.Loading import com.airbnb.mvrx.Success +import com.airbnb.mvrx.Uninitialized import im.vector.app.R import im.vector.app.core.epoxy.loadingItem import im.vector.app.core.error.ErrorFormatter @@ -89,19 +90,20 @@ class SoftLogoutController @Inject constructor( private fun buildForm(state: SoftLogoutViewState) { val host = this when (state.asyncHomeServerLoginFlowRequest) { - is Incomplete -> { + Uninitialized, + is Loading -> { loadingItem { id("loading") } } - is Fail -> { + is Fail -> { loginErrorWithRetryItem { id("errorRetry") text(host.errorFormatter.toHumanReadable(state.asyncHomeServerLoginFlowRequest.error)) listener { host.listener?.retry() } } } - is Success -> { + is Success -> { when (state.asyncHomeServerLoginFlowRequest.invoke()) { LoginMode.Password -> { loginPasswordFormItem { diff --git a/vector/src/main/java/im/vector/app/features/signout/soft/SoftLogoutFragment.kt b/vector/src/main/java/im/vector/app/features/signout/soft/SoftLogoutFragment.kt index f40f35a6e2..8a682b4b5e 100644 --- a/vector/src/main/java/im/vector/app/features/signout/soft/SoftLogoutFragment.kt +++ b/vector/src/main/java/im/vector/app/features/signout/soft/SoftLogoutFragment.kt @@ -65,14 +65,14 @@ class SoftLogoutFragment @Inject constructor( mode.ssoIdentityProviders )) } - is LoginMode.Sso -> { + is LoginMode.Sso -> { loginViewModel.handle(LoginAction.SetupSsoForSessionRecovery( softLogoutViewState.homeServerUrl, softLogoutViewState.deviceId, mode.ssoIdentityProviders )) } - LoginMode.Unsupported -> { + LoginMode.Unsupported -> { // Prepare the loginViewModel for a SSO/login fallback recovery loginViewModel.handle(LoginAction.SetupSsoForSessionRecovery( softLogoutViewState.homeServerUrl, @@ -80,7 +80,7 @@ class SoftLogoutFragment @Inject constructor( null )) } - else -> Unit + else -> Unit } } } diff --git a/vector/src/main/java/im/vector/app/features/signout/soft/SoftLogoutViewModel.kt b/vector/src/main/java/im/vector/app/features/signout/soft/SoftLogoutViewModel.kt index 00422d8872..5f31e6b508 100644 --- a/vector/src/main/java/im/vector/app/features/signout/soft/SoftLogoutViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/signout/soft/SoftLogoutViewModel.kt @@ -37,6 +37,7 @@ import kotlinx.coroutines.launch import org.matrix.android.sdk.api.auth.AuthenticationService import org.matrix.android.sdk.api.auth.data.LoginFlowTypes import org.matrix.android.sdk.api.session.Session +import org.matrix.android.sdk.api.session.getUser import timber.log.Timber /** @@ -169,7 +170,7 @@ class SoftLogoutViewModel @AssistedInject constructor( } viewModelScope.launch { try { - session.updateCredentials(action.credentials) + session.signOutService().updateCredentials(action.credentials) onSessionRestored() } catch (failure: Throwable) { _viewEvents.post(SoftLogoutViewEvents.Failure(failure)) @@ -192,7 +193,7 @@ class SoftLogoutViewModel @AssistedInject constructor( } viewModelScope.launch { try { - session.signInAgain(action.password) + session.signOutService().signInAgain(action.password) onSessionRestored() } catch (failure: Throwable) { setState { diff --git a/vector/src/main/java/im/vector/app/features/spaces/SpaceBetaHeaderItem.kt b/vector/src/main/java/im/vector/app/features/spaces/SpaceBetaHeaderItem.kt index abe7e8d2a8..667f895d4d 100644 --- a/vector/src/main/java/im/vector/app/features/spaces/SpaceBetaHeaderItem.kt +++ b/vector/src/main/java/im/vector/app/features/spaces/SpaceBetaHeaderItem.kt @@ -16,27 +16,12 @@ package im.vector.app.features.spaces -import android.view.View -import com.airbnb.epoxy.EpoxyAttribute import com.airbnb.epoxy.EpoxyModelClass import im.vector.app.R -import im.vector.app.core.epoxy.ClickListener import im.vector.app.core.epoxy.VectorEpoxyHolder import im.vector.app.core.epoxy.VectorEpoxyModel -import im.vector.app.core.epoxy.onClick @EpoxyModelClass(layout = R.layout.item_space_beta_header) abstract class SpaceBetaHeaderItem : VectorEpoxyModel() { - - @EpoxyAttribute(EpoxyAttribute.Option.DoNotHash) - var clickAction: ClickListener? = null - - override fun bind(holder: Holder) { - super.bind(holder) - holder.feedBackAction.onClick(clickAction) - } - - class Holder : VectorEpoxyHolder() { - val feedBackAction by bind(R.id.spaceBetaFeedbackAction) - } + class Holder : VectorEpoxyHolder() } diff --git a/vector/src/main/java/im/vector/app/features/spaces/SpaceListFragment.kt b/vector/src/main/java/im/vector/app/features/spaces/SpaceListFragment.kt index dff98722eb..1fc131ca86 100644 --- a/vector/src/main/java/im/vector/app/features/spaces/SpaceListFragment.kt +++ b/vector/src/main/java/im/vector/app/features/spaces/SpaceListFragment.kt @@ -22,13 +22,13 @@ import android.view.LayoutInflater import android.view.View import android.view.ViewGroup import com.airbnb.epoxy.EpoxyTouchHelper -import com.airbnb.mvrx.Incomplete +import com.airbnb.mvrx.Loading import com.airbnb.mvrx.Success +import com.airbnb.mvrx.Uninitialized import com.airbnb.mvrx.fragmentViewModel import com.airbnb.mvrx.withState import im.vector.app.core.extensions.cleanup import im.vector.app.core.extensions.configureWith -import im.vector.app.core.extensions.exhaustive import im.vector.app.core.platform.StateView import im.vector.app.core.platform.VectorBaseFragment import im.vector.app.databinding.FragmentGroupListBinding @@ -109,7 +109,7 @@ class SpaceListFragment @Inject constructor( is SpaceListViewEvents.AddSpace -> sharedActionViewModel.post(HomeActivitySharedAction.AddSpace) is SpaceListViewEvents.OpenGroup -> sharedActionViewModel.post(HomeActivitySharedAction.OpenGroup(it.groupingMethodHasChanged)) is SpaceListViewEvents.OpenSpaceInvite -> sharedActionViewModel.post(HomeActivitySharedAction.OpenSpaceInvite(it.id)) - }.exhaustive + } } } @@ -121,8 +121,10 @@ class SpaceListFragment @Inject constructor( override fun invalidate() = withState(viewModel) { state -> when (state.asyncSpaces) { - is Incomplete -> views.stateView.state = StateView.State.Loading - is Success -> views.stateView.state = StateView.State.Content + Uninitialized, + is Loading -> views.stateView.state = StateView.State.Loading + is Success -> views.stateView.state = StateView.State.Content + else -> Unit } spaceController.update(state) } diff --git a/vector/src/main/java/im/vector/app/features/spaces/SpaceListViewModel.kt b/vector/src/main/java/im/vector/app/features/spaces/SpaceListViewModel.kt index 20af5b5827..84363897e2 100644 --- a/vector/src/main/java/im/vector/app/features/spaces/SpaceListViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/spaces/SpaceListViewModel.kt @@ -28,6 +28,8 @@ import im.vector.app.RoomGroupingMethod import im.vector.app.core.di.MavericksAssistedViewModelFactory import im.vector.app.core.di.hiltMavericksViewModelFactory import im.vector.app.core.platform.VectorViewModel +import im.vector.app.features.analytics.AnalyticsTracker +import im.vector.app.features.analytics.plan.Interaction import im.vector.app.features.invite.AutoAcceptInvites import im.vector.app.features.session.coroutineScope import im.vector.app.features.settings.VectorPreferences @@ -38,7 +40,6 @@ import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.distinctUntilChanged import kotlinx.coroutines.flow.flowOn import kotlinx.coroutines.flow.launchIn -import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.flow.sample import kotlinx.coroutines.launch @@ -48,11 +49,13 @@ import org.matrix.android.sdk.api.query.QueryStringValue import org.matrix.android.sdk.api.session.Session import org.matrix.android.sdk.api.session.events.model.toContent import org.matrix.android.sdk.api.session.events.model.toModel +import org.matrix.android.sdk.api.session.getRoom import org.matrix.android.sdk.api.session.group.groupSummaryQueryParams import org.matrix.android.sdk.api.session.room.RoomSortOrder import org.matrix.android.sdk.api.session.room.accountdata.RoomAccountDataTypes import org.matrix.android.sdk.api.session.room.model.Membership import org.matrix.android.sdk.api.session.room.roomSummaryQueryParams +import org.matrix.android.sdk.api.session.room.spaceSummaryQueryParams import org.matrix.android.sdk.api.session.room.summary.RoomAggregateNotificationCount import org.matrix.android.sdk.api.session.space.SpaceOrderUtils import org.matrix.android.sdk.api.session.space.model.SpaceOrderContent @@ -64,7 +67,8 @@ class SpaceListViewModel @AssistedInject constructor(@Assisted initialState: Spa private val appStateHandler: AppStateHandler, private val session: Session, private val vectorPreferences: VectorPreferences, - private val autoAcceptInvites: AutoAcceptInvites + private val autoAcceptInvites: AutoAcceptInvites, + private val analyticsTracker: AnalyticsTracker ) : VectorViewModel(initialState) { @AssistedFactory @@ -78,7 +82,7 @@ class SpaceListViewModel @AssistedInject constructor(@Assisted initialState: Spa init { - session.getUserLive(session.myUserId) + session.userService().getUserLive(session.myUserId) .asFlow() .setOnEach { copy( @@ -96,14 +100,14 @@ class SpaceListViewModel @AssistedInject constructor(@Assisted initialState: Spa ) } - session.getGroupSummariesLive(groupSummaryQueryParams {}) + session.groupService().getGroupSummariesLive(groupSummaryQueryParams {}) .asFlow() .setOnEach { copy(legacyGroups = it) } // XXX there should be a way to refactor this and share it - session.getPagedRoomSummariesLive( + session.roomService().getPagedRoomSummariesLive( roomSummaryQueryParams { this.memberships = listOf(Membership.JOIN) this.activeSpaceFilter = ActiveSpaceFilter.ActiveSpace(null).takeIf { @@ -116,11 +120,11 @@ class SpaceListViewModel @AssistedInject constructor(@Assisted initialState: Spa val inviteCount = if (autoAcceptInvites.hideInvites) { 0 } else { - session.getRoomSummaries( + session.roomService().getRoomSummaries( roomSummaryQueryParams { this.memberships = listOf(Membership.INVITE) } ).size } - val totalCount = session.getNotificationCountForRooms( + val totalCount = session.roomService().getNotificationCountForRooms( roomSummaryQueryParams { this.memberships = listOf(Membership.JOIN) this.activeSpaceFilter = ActiveSpaceFilter.ActiveSpace(null).takeIf { @@ -208,7 +212,8 @@ class SpaceListViewModel @AssistedInject constructor(@Assisted initialState: Spa } session.coroutineScope.launch { orderCommands.forEach { - session.getRoom(it.spaceId)?.updateAccountData(RoomAccountDataTypes.EVENT_TYPE_SPACE_ORDER, + session.getRoom(it.spaceId)?.updateAccountData( + RoomAccountDataTypes.EVENT_TYPE_SPACE_ORDER, SpaceOrderContent(order = it.order).toContent() ) } @@ -225,9 +230,12 @@ class SpaceListViewModel @AssistedInject constructor(@Assisted initialState: Spa private fun handleSelectSpace(action: SpaceListAction.SelectSpace) = withState { state -> val groupingMethod = state.selectedGroupingMethod if (groupingMethod is RoomGroupingMethod.ByLegacyGroup || groupingMethod.space()?.roomId != action.spaceSummary?.roomId) { + analyticsTracker.capture(Interaction(null, null, Interaction.Name.SpacePanelSwitchSpace)) setState { copy(selectedGroupingMethod = RoomGroupingMethod.BySpace(action.spaceSummary)) } appStateHandler.setCurrentSpace(action.spaceSummary?.roomId) _viewEvents.post(SpaceListViewEvents.OpenSpace(groupingMethod is RoomGroupingMethod.ByLegacyGroup)) + } else { + analyticsTracker.capture(Interaction(null, null, Interaction.Name.SpacePanelSelectedSpace)) } } @@ -266,38 +274,28 @@ class SpaceListViewModel @AssistedInject constructor(@Assisted initialState: Spa } private fun observeSpaceSummaries() { - val spaceSummaryQueryParams = roomSummaryQueryParams { + val params = spaceSummaryQueryParams { memberships = listOf(Membership.JOIN, Membership.INVITE) displayName = QueryStringValue.IsNotEmpty - excludeType = listOf(/**RoomType.MESSAGING,$*/ - null) } - val flowSession = session.flow() - combine( - flowSession - .liveUser(session.myUserId) - .map { - it.getOrNull() - }, - flowSession - .liveSpaceSummaries(spaceSummaryQueryParams), - session - .accountDataService() + session.flow() + .liveSpaceSummaries(params), + session.accountDataService() .getLiveRoomAccountDataEvents(setOf(RoomAccountDataTypes.EVENT_TYPE_SPACE_ORDER)) .asFlow() - ) { _, communityGroups, _ -> - communityGroups + ) { spaces, _ -> + spaces } .execute { async -> - val rootSpaces = session.spaceService().getRootSpaceSummaries() - val orders = rootSpaces.map { + val rootSpaces = async.invoke().orEmpty().filter { it.flattenParentIds.isEmpty() } + val orders = rootSpaces.associate { it.roomId to session.getRoom(it.roomId) ?.getAccountDataEvent(RoomAccountDataTypes.EVENT_TYPE_SPACE_ORDER) ?.content.toModel() ?.safeOrder() - }.toMap() + } copy( asyncSpaces = async, rootSpacesOrdered = rootSpaces.sortedWith(TopLevelSpaceComparator(orders)), diff --git a/vector/src/main/java/im/vector/app/features/spaces/SpaceMenuViewModel.kt b/vector/src/main/java/im/vector/app/features/spaces/SpaceMenuViewModel.kt index 9b95b5328f..4e1489ef9b 100644 --- a/vector/src/main/java/im/vector/app/features/spaces/SpaceMenuViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/spaces/SpaceMenuViewModel.kt @@ -35,8 +35,11 @@ import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.launch import org.matrix.android.sdk.api.query.ActiveSpaceFilter +import org.matrix.android.sdk.api.query.RoomCategoryFilter import org.matrix.android.sdk.api.session.Session import org.matrix.android.sdk.api.session.events.model.EventType +import org.matrix.android.sdk.api.session.getRoom +import org.matrix.android.sdk.api.session.getRoomSummary import org.matrix.android.sdk.api.session.room.model.Membership import org.matrix.android.sdk.api.session.room.powerlevels.PowerLevelsHelper import org.matrix.android.sdk.api.session.room.powerlevels.Role @@ -135,11 +138,12 @@ class SpaceMenuViewModel @AssistedInject constructor( } else if (state.leaveMode == SpaceMenuState.LeaveMode.LEAVE_ALL) { // need to find all child rooms that i have joined - session.getRoomSummaries( + session.roomService().getRoomSummaries( roomSummaryQueryParams { excludeType = null activeSpaceFilter = ActiveSpaceFilter.ActiveSpace(initialState.spaceId) memberships = listOf(Membership.JOIN) + roomCategoryFilter = RoomCategoryFilter.ONLY_ROOMS } ).forEach { try { diff --git a/vector/src/main/java/im/vector/app/features/spaces/SpaceSettingsMenuBottomSheet.kt b/vector/src/main/java/im/vector/app/features/spaces/SpaceSettingsMenuBottomSheet.kt index 7449868292..78eab5b97f 100644 --- a/vector/src/main/java/im/vector/app/features/spaces/SpaceSettingsMenuBottomSheet.kt +++ b/vector/src/main/java/im/vector/app/features/spaces/SpaceSettingsMenuBottomSheet.kt @@ -30,6 +30,7 @@ import dagger.hilt.android.AndroidEntryPoint import im.vector.app.core.extensions.setTextOrHide import im.vector.app.core.platform.VectorBaseBottomSheetDialogFragment import im.vector.app.databinding.BottomSheetSpaceSettingsBinding +import im.vector.app.features.analytics.plan.MobileScreen import im.vector.app.features.home.AvatarRenderer import im.vector.app.features.navigation.Navigator import im.vector.app.features.rageshake.BugReporter @@ -71,6 +72,11 @@ class SpaceSettingsMenuBottomSheet : VectorBaseBottomSheetDialogFragment = Uninitialized, val nameInlineError: String? = null, - val defaultRooms: Map? = null, - val default3pidInvite: Map? = null, - val emailValidationResult: Map? = null, + val defaultRooms: Map? = null, // Int: position in form + val default3pidInvite: Map? = null, // Int: position in form + val emailValidationResult: Map? = null, // Int: position in form val creationResult: Async = Uninitialized, val canInviteByMail: Boolean = false ) : MavericksState { diff --git a/vector/src/main/java/im/vector/app/features/spaces/create/CreateSpaceViewModel.kt b/vector/src/main/java/im/vector/app/features/spaces/create/CreateSpaceViewModel.kt index 8ddeab3223..46da2f5826 100644 --- a/vector/src/main/java/im/vector/app/features/spaces/create/CreateSpaceViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/spaces/create/CreateSpaceViewModel.kt @@ -29,7 +29,6 @@ import im.vector.app.R import im.vector.app.core.di.MavericksAssistedViewModelFactory import im.vector.app.core.di.hiltMavericksViewModelFactory import im.vector.app.core.error.ErrorFormatter -import im.vector.app.core.extensions.exhaustive import im.vector.app.core.extensions.isEmail import im.vector.app.core.platform.VectorViewModel import im.vector.app.core.resources.StringProvider @@ -192,7 +191,7 @@ class CreateSpaceViewModel @AssistedInject constructor( is CreateSpaceAction.SetSpaceTopology -> { handleSetTopology(action) } - }.exhaustive + } } private fun handleSetTopology(action: CreateSpaceAction.SetSpaceTopology) { @@ -316,7 +315,7 @@ class CreateSpaceViewModel @AssistedInject constructor( } viewModelScope.launch { try { - when (val result = session.checkAliasAvailability(aliasLocalPart)) { + when (val result = session.roomDirectoryService().checkAliasAvailability(aliasLocalPart)) { AliasAvailabilityResult.Available -> { setState { copy( @@ -374,7 +373,7 @@ class CreateSpaceViewModel @AssistedInject constructor( ) ) when (result) { - is CreateSpaceTaskResult.Success -> { + is CreateSpaceTaskResult.Success -> { setState { copy(creationResult = Success(result.spaceId)) } diff --git a/vector/src/main/java/im/vector/app/features/spaces/create/CreateSpaceViewModelTask.kt b/vector/src/main/java/im/vector/app/features/spaces/create/CreateSpaceViewModelTask.kt index d9c18db01d..c7777649bd 100644 --- a/vector/src/main/java/im/vector/app/features/spaces/create/CreateSpaceViewModelTask.kt +++ b/vector/src/main/java/im/vector/app/features/spaces/create/CreateSpaceViewModelTask.kt @@ -113,7 +113,7 @@ class CreateSpaceViewModelTask @Inject constructor( try { val roomId = try { if (params.isPublic) { - session.createRoom( + session.roomService().createRoom( CreateRoomParams().apply { this.name = roomName this.preset = CreateRoomPreset.PRESET_PUBLIC_CHAT @@ -121,13 +121,14 @@ class CreateSpaceViewModelTask @Inject constructor( ) } else { val homeServerCapabilities = session + .homeServerCapabilitiesService() .getHomeServerCapabilities() val restrictedSupport = homeServerCapabilities .isFeatureSupported(HomeServerCapabilities.ROOM_CAP_RESTRICTED) val createRestricted = restrictedSupport == HomeServerCapabilities.RoomCapabilitySupport.SUPPORTED if (createRestricted) { - session.createRoom(CreateRoomParams().apply { + session.roomService().createRoom(CreateRoomParams().apply { this.name = roomName this.featurePreset = RestrictedRoomPreset( homeServerCapabilities, @@ -140,7 +141,7 @@ class CreateSpaceViewModelTask @Inject constructor( } }) } else { - session.createRoom(CreateRoomParams().apply { + session.roomService().createRoom(CreateRoomParams().apply { this.name = roomName visibility = RoomDirectoryVisibility.PRIVATE this.preset = CreateRoomPreset.PRESET_PRIVATE_CHAT diff --git a/vector/src/main/java/im/vector/app/features/spaces/create/WizardButtonView.kt b/vector/src/main/java/im/vector/app/features/spaces/create/WizardButtonView.kt index 971d61dd9c..c1bfba7205 100644 --- a/vector/src/main/java/im/vector/app/features/spaces/create/WizardButtonView.kt +++ b/vector/src/main/java/im/vector/app/features/spaces/create/WizardButtonView.kt @@ -30,7 +30,7 @@ import im.vector.app.core.extensions.setTextOrHide import im.vector.app.databinding.ViewSpaceTypeButtonBinding class WizardButtonView @JvmOverloads constructor(context: Context, attrs: AttributeSet? = null, defStyle: Int = 0) : - ConstraintLayout(context, attrs, defStyle) { + ConstraintLayout(context, attrs, defStyle) { private val views: ViewSpaceTypeButtonBinding diff --git a/vector/src/main/java/im/vector/app/features/spaces/explore/SpaceDirectoryFragment.kt b/vector/src/main/java/im/vector/app/features/spaces/explore/SpaceDirectoryFragment.kt index 955fedd7dc..e59087778f 100644 --- a/vector/src/main/java/im/vector/app/features/spaces/explore/SpaceDirectoryFragment.kt +++ b/vector/src/main/java/im/vector/app/features/spaces/explore/SpaceDirectoryFragment.kt @@ -42,6 +42,7 @@ import im.vector.app.core.utils.colorizeMatchingText import im.vector.app.core.utils.isValidUrl import im.vector.app.core.utils.openUrlInExternalBrowser import im.vector.app.databinding.FragmentSpaceDirectoryBinding +import im.vector.app.features.analytics.plan.MobileScreen import im.vector.app.features.home.room.detail.timeline.TimelineEventController import im.vector.app.features.matrixto.SpaceCardRenderer import im.vector.app.features.permalink.PermalinkHandler @@ -79,6 +80,7 @@ class SpaceDirectoryFragment @Inject constructor( override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) + analyticsScreenName = MobileScreen.ScreenName.SpaceExploreRooms childFragmentManager.setFragmentResultListener(SpaceAddRoomSpaceChooserBottomSheet.REQUEST_KEY, this) { _, bundle -> bundle.getString(SpaceAddRoomSpaceChooserBottomSheet.BUNDLE_KEY_ACTION)?.let { action -> @@ -167,7 +169,7 @@ class SpaceDirectoryFragment @Inject constructor( } else { toolbar?.title = state.currentRootSummary?.name ?: state.currentRootSummary?.canonicalAlias - ?: getString(R.string.space_explore_activity_title) + ?: getString(R.string.space_explore_activity_title) } spaceCardRenderer.render(state.currentRootSummary, emptyList(), this, views.spaceCard, showDescription = false) diff --git a/vector/src/main/java/im/vector/app/features/spaces/explore/SpaceDirectoryViewModel.kt b/vector/src/main/java/im/vector/app/features/spaces/explore/SpaceDirectoryViewModel.kt index abc70ccbc1..fe43ad7997 100644 --- a/vector/src/main/java/im/vector/app/features/spaces/explore/SpaceDirectoryViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/spaces/explore/SpaceDirectoryViewModel.kt @@ -35,6 +35,8 @@ import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.launch import org.matrix.android.sdk.api.session.Session import org.matrix.android.sdk.api.session.events.model.EventType +import org.matrix.android.sdk.api.session.getRoom +import org.matrix.android.sdk.api.session.getRoomSummary import org.matrix.android.sdk.api.session.room.model.Membership import org.matrix.android.sdk.api.session.room.model.RoomJoinRules import org.matrix.android.sdk.api.session.room.model.RoomSummary @@ -275,9 +277,9 @@ class SpaceDirectoryViewModel @AssistedInject constructor( knownSummaries = ( knownSummaries + (paginate.children.mapNotNull { - session.getRoomSummary(it.childRoomId) - ?.takeIf { it.membership == Membership.JOIN } // only take if joined because it will be up to date (synced) - }) + session.getRoomSummary(it.childRoomId) + ?.takeIf { it.membership == Membership.JOIN } // only take if joined because it will be up to date (synced) + }) ).distinctBy { it.roomId } query = query.copy( @@ -409,7 +411,7 @@ class SpaceDirectoryViewModel @AssistedInject constructor( if (isSpace) { session.spaceService().joinSpace(childId, null, spaceChildInfo.viaServers) } else { - session.joinRoom(childId, null, spaceChildInfo.viaServers) + session.roomService().joinRoom(childId, null, spaceChildInfo.viaServers) } } catch (failure: Throwable) { Timber.e(failure, "## Space: Failed to join room or subspace") diff --git a/vector/src/main/java/im/vector/app/features/spaces/invite/SpaceInviteBottomSheetViewModel.kt b/vector/src/main/java/im/vector/app/features/spaces/invite/SpaceInviteBottomSheetViewModel.kt index 9c6b092135..93bf51368b 100644 --- a/vector/src/main/java/im/vector/app/features/spaces/invite/SpaceInviteBottomSheetViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/spaces/invite/SpaceInviteBottomSheetViewModel.kt @@ -16,7 +16,6 @@ package im.vector.app.features.spaces.invite -import androidx.lifecycle.viewModelScope import com.airbnb.mvrx.Fail import com.airbnb.mvrx.Loading import com.airbnb.mvrx.MavericksViewModelFactory @@ -34,6 +33,8 @@ import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.launch import org.matrix.android.sdk.api.extensions.tryOrNull import org.matrix.android.sdk.api.session.Session +import org.matrix.android.sdk.api.session.getRoomSummary +import org.matrix.android.sdk.api.session.getUser import org.matrix.android.sdk.api.session.room.model.Membership import org.matrix.android.sdk.api.session.room.model.RoomSummary import org.matrix.android.sdk.api.session.room.peeking.PeekResult @@ -47,7 +48,7 @@ class SpaceInviteBottomSheetViewModel @AssistedInject constructor( init { session.getRoomSummary(initialState.spaceId)?.let { roomSummary -> val knownMembers = roomSummary.otherMemberIds.filter { - session.getExistingDirectRoomWithUser(it) != null + session.roomService().getExistingDirectRoomWithUser(it) != null }.mapNotNull { session.getUser(it) } // put one with avatar first, and take 5 val peopleYouKnow = (knownMembers.filter { it.avatarUrl != null } + knownMembers.filter { it.avatarUrl == null }) @@ -71,7 +72,7 @@ class SpaceInviteBottomSheetViewModel @AssistedInject constructor( */ private fun getLatestRoomSummary(roomSummary: RoomSummary) { viewModelScope.launch(Dispatchers.IO) { - val peekResult = tryOrNull { session.peekRoom(roomSummary.roomId) } as? PeekResult.Success ?: return@launch + val peekResult = tryOrNull { session.roomService().peekRoom(roomSummary.roomId) } as? PeekResult.Success ?: return@launch setState { copy( summary = Success( diff --git a/vector/src/main/java/im/vector/app/features/spaces/leave/SpaceLeaveAdvancedViewModel.kt b/vector/src/main/java/im/vector/app/features/spaces/leave/SpaceLeaveAdvancedViewModel.kt index fc241e711c..3f5a27f696 100644 --- a/vector/src/main/java/im/vector/app/features/spaces/leave/SpaceLeaveAdvancedViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/spaces/leave/SpaceLeaveAdvancedViewModel.kt @@ -36,6 +36,8 @@ import okhttp3.internal.toImmutableList import org.matrix.android.sdk.api.query.ActiveSpaceFilter import org.matrix.android.sdk.api.query.RoomCategoryFilter import org.matrix.android.sdk.api.session.Session +import org.matrix.android.sdk.api.session.getRoom +import org.matrix.android.sdk.api.session.getRoomSummary import org.matrix.android.sdk.api.session.room.model.Membership import org.matrix.android.sdk.api.session.room.roomSummaryQueryParams import org.matrix.android.sdk.flow.flow @@ -72,7 +74,7 @@ class SpaceLeaveAdvancedViewModel @AssistedInject constructor( try { state.selectedRooms.forEach { try { - session.leaveRoom(it) + session.roomService().leaveRoom(it) } catch (failure: Throwable) { // silently ignore? Timber.e(failure, "Fail to leave sub rooms/spaces") @@ -110,7 +112,7 @@ class SpaceLeaveAdvancedViewModel @AssistedInject constructor( } viewModelScope.launch { - val children = session.getRoomSummaries( + val children = session.roomService().getRoomSummaries( roomSummaryQueryParams { includeType = null memberships = listOf(Membership.JOIN) diff --git a/vector/src/main/java/im/vector/app/features/spaces/manage/SpaceAddRoomFragment.kt b/vector/src/main/java/im/vector/app/features/spaces/manage/SpaceAddRoomFragment.kt index 8d6a351013..0c4dcbf90f 100644 --- a/vector/src/main/java/im/vector/app/features/spaces/manage/SpaceAddRoomFragment.kt +++ b/vector/src/main/java/im/vector/app/features/spaces/manage/SpaceAddRoomFragment.kt @@ -38,7 +38,6 @@ import im.vector.app.core.platform.OnBackPressed import im.vector.app.core.platform.VectorBaseFragment import im.vector.app.databinding.FragmentSpaceAddRoomsBinding import kotlinx.coroutines.flow.Flow -import kotlinx.coroutines.flow.collect import kotlinx.coroutines.flow.debounce import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.onEach diff --git a/vector/src/main/java/im/vector/app/features/spaces/manage/SpaceAddRoomsViewModel.kt b/vector/src/main/java/im/vector/app/features/spaces/manage/SpaceAddRoomsViewModel.kt index 7d99c53f23..0be0fb0464 100644 --- a/vector/src/main/java/im/vector/app/features/spaces/manage/SpaceAddRoomsViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/spaces/manage/SpaceAddRoomsViewModel.kt @@ -39,6 +39,7 @@ import org.matrix.android.sdk.api.query.ActiveSpaceFilter import org.matrix.android.sdk.api.query.QueryStringValue import org.matrix.android.sdk.api.query.RoomCategoryFilter import org.matrix.android.sdk.api.session.Session +import org.matrix.android.sdk.api.session.getRoomSummary import org.matrix.android.sdk.api.session.room.RoomSortOrder import org.matrix.android.sdk.api.session.room.UpdatableLivePageResult import org.matrix.android.sdk.api.session.room.model.Membership @@ -63,8 +64,10 @@ class SpaceAddRoomsViewModel @AssistedInject constructor( companion object : MavericksViewModelFactory by hiltMavericksViewModelFactory() + private val roomService = session.roomService() + val spaceUpdatableLivePageResult: UpdatableLivePageResult by lazy { - session.getFilteredPagedRoomSummariesLive( + roomService.getFilteredPagedRoomSummariesLive( roomSummaryQueryParams { this.memberships = listOf(Membership.JOIN) this.excludeType = null @@ -84,12 +87,12 @@ class SpaceAddRoomsViewModel @AssistedInject constructor( val spaceCountFlow: Flow by lazy { spaceUpdatableLivePageResult.livePagedList.asFlow() - .flatMapLatest { session.getRoomCountFlow(spaceUpdatableLivePageResult.queryParams) } + .flatMapLatest { roomService.getRoomCountLive(spaceUpdatableLivePageResult.queryParams).asFlow() } .distinctUntilChanged() } val roomUpdatableLivePageResult: UpdatableLivePageResult by lazy { - session.getFilteredPagedRoomSummariesLive( + roomService.getFilteredPagedRoomSummariesLive( roomSummaryQueryParams { this.memberships = listOf(Membership.JOIN) this.excludeType = listOf(RoomType.SPACE) @@ -110,12 +113,12 @@ class SpaceAddRoomsViewModel @AssistedInject constructor( val roomCountFlow: Flow by lazy { roomUpdatableLivePageResult.livePagedList.asFlow() - .flatMapLatest { session.getRoomCountFlow(roomUpdatableLivePageResult.queryParams) } + .flatMapLatest { roomService.getRoomCountLive(roomUpdatableLivePageResult.queryParams).asFlow() } .distinctUntilChanged() } val dmUpdatableLivePageResult: UpdatableLivePageResult by lazy { - session.getFilteredPagedRoomSummariesLive( + roomService.getFilteredPagedRoomSummariesLive( roomSummaryQueryParams { this.memberships = listOf(Membership.JOIN) this.excludeType = listOf(RoomType.SPACE) @@ -136,7 +139,7 @@ class SpaceAddRoomsViewModel @AssistedInject constructor( val dmCountFlow: Flow by lazy { dmUpdatableLivePageResult.livePagedList.asFlow() - .flatMapLatest { session.getRoomCountFlow(dmUpdatableLivePageResult.queryParams) } + .flatMapLatest { roomService.getRoomCountLive(dmUpdatableLivePageResult.queryParams).asFlow() } .distinctUntilChanged() } diff --git a/vector/src/main/java/im/vector/app/features/spaces/manage/SpaceManageActivity.kt b/vector/src/main/java/im/vector/app/features/spaces/manage/SpaceManageActivity.kt index 85f80960b0..12ae8fc1f9 100644 --- a/vector/src/main/java/im/vector/app/features/spaces/manage/SpaceManageActivity.kt +++ b/vector/src/main/java/im/vector/app/features/spaces/manage/SpaceManageActivity.kt @@ -85,6 +85,7 @@ class SpaceManageActivity : VectorBaseActivity() { when (sharedAction) { is RoomDirectorySharedAction.Back, is RoomDirectorySharedAction.Close -> finish() + else -> Unit } } .launchIn(lifecycleScope) diff --git a/vector/src/main/java/im/vector/app/features/spaces/manage/SpaceManageRoomViewEvents.kt b/vector/src/main/java/im/vector/app/features/spaces/manage/SpaceManageRoomViewEvents.kt index 8ba7398a2f..043c71f549 100644 --- a/vector/src/main/java/im/vector/app/features/spaces/manage/SpaceManageRoomViewEvents.kt +++ b/vector/src/main/java/im/vector/app/features/spaces/manage/SpaceManageRoomViewEvents.kt @@ -19,6 +19,6 @@ package im.vector.app.features.spaces.manage import im.vector.app.core.platform.VectorViewEvents sealed class SpaceManageRoomViewEvents : VectorViewEvents { -// object BulkActionSuccess: SpaceManageRoomViewEvents() + // object BulkActionSuccess: SpaceManageRoomViewEvents() data class BulkActionFailure(val errorList: List) : SpaceManageRoomViewEvents() } diff --git a/vector/src/main/java/im/vector/app/features/spaces/manage/SpaceManageRoomsViewModel.kt b/vector/src/main/java/im/vector/app/features/spaces/manage/SpaceManageRoomsViewModel.kt index a1dd26a936..6896b85924 100644 --- a/vector/src/main/java/im/vector/app/features/spaces/manage/SpaceManageRoomsViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/spaces/manage/SpaceManageRoomsViewModel.kt @@ -33,6 +33,7 @@ import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.launch import org.matrix.android.sdk.api.extensions.tryOrNull import org.matrix.android.sdk.api.session.Session +import org.matrix.android.sdk.api.session.getRoomSummary class SpaceManageRoomsViewModel @AssistedInject constructor( @Assisted val initialState: SpaceManageRoomViewState, diff --git a/vector/src/main/java/im/vector/app/features/spaces/manage/SpaceManageSharedViewModel.kt b/vector/src/main/java/im/vector/app/features/spaces/manage/SpaceManageSharedViewModel.kt index bedd1873e8..2a2598075f 100644 --- a/vector/src/main/java/im/vector/app/features/spaces/manage/SpaceManageSharedViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/spaces/manage/SpaceManageSharedViewModel.kt @@ -22,7 +22,6 @@ import dagger.assisted.AssistedFactory import dagger.assisted.AssistedInject import im.vector.app.core.di.MavericksAssistedViewModelFactory import im.vector.app.core.di.hiltMavericksViewModelFactory -import im.vector.app.core.extensions.exhaustive import im.vector.app.core.platform.VectorViewModel import org.matrix.android.sdk.api.session.Session @@ -51,6 +50,6 @@ class SpaceManageSharedViewModel @AssistedInject constructor( SpaceManagedSharedAction.ManageRooms -> _viewEvents.post(SpaceManagedSharedViewEvents.NavigateToManageRooms) SpaceManagedSharedAction.OpenSpaceAliasesSettings -> _viewEvents.post(SpaceManagedSharedViewEvents.NavigateToAliasSettings) SpaceManagedSharedAction.OpenSpacePermissionSettings -> _viewEvents.post(SpaceManagedSharedViewEvents.NavigateToPermissionSettings) - }.exhaustive + } } } diff --git a/vector/src/main/java/im/vector/app/features/spaces/manage/SpaceManageViewState.kt b/vector/src/main/java/im/vector/app/features/spaces/manage/SpaceManageViewState.kt index 82abc823c3..0baa4d309f 100644 --- a/vector/src/main/java/im/vector/app/features/spaces/manage/SpaceManageViewState.kt +++ b/vector/src/main/java/im/vector/app/features/spaces/manage/SpaceManageViewState.kt @@ -24,6 +24,7 @@ enum class ManageType { Settings, ManageRooms } + data class SpaceManageViewState( val spaceId: String = "", val manageType: ManageType diff --git a/vector/src/main/java/im/vector/app/features/spaces/manage/SpaceSettingsController.kt b/vector/src/main/java/im/vector/app/features/spaces/manage/SpaceSettingsController.kt index 7cd5a70279..afe5200fbb 100644 --- a/vector/src/main/java/im/vector/app/features/spaces/manage/SpaceSettingsController.kt +++ b/vector/src/main/java/im/vector/app/features/spaces/manage/SpaceSettingsController.kt @@ -65,13 +65,13 @@ class SpaceSettingsController @Inject constructor( id("avatar") enabled(data.actionPermissions.canChangeAvatar) when (val avatarAction = data.avatarAction) { - RoomSettingsViewState.AvatarAction.None -> { + RoomSettingsViewState.AvatarAction.None -> { // Use the current value avatarRenderer(host.avatarRenderer) // We do not want to use the fallback avatar url, which can be the other user avatar, or the current user avatar. matrixItem(roomSummary.toMatrixItem().updateAvatar(data.currentRoomAvatarUrl)) } - RoomSettingsViewState.AvatarAction.DeleteAvatar -> + RoomSettingsViewState.AvatarAction.DeleteAvatar -> imageUri(null) is RoomSettingsViewState.AvatarAction.UpdateAvatar -> imageUri(avatarAction.newAvatarUri) diff --git a/vector/src/main/java/im/vector/app/features/spaces/manage/SpaceSettingsFragment.kt b/vector/src/main/java/im/vector/app/features/spaces/manage/SpaceSettingsFragment.kt index 266d08fd12..57b1c97efb 100644 --- a/vector/src/main/java/im/vector/app/features/spaces/manage/SpaceSettingsFragment.kt +++ b/vector/src/main/java/im/vector/app/features/spaces/manage/SpaceSettingsFragment.kt @@ -34,7 +34,6 @@ import im.vector.app.R import im.vector.app.core.dialogs.GalleryOrCameraDialogHelper import im.vector.app.core.extensions.cleanup import im.vector.app.core.extensions.configureWith -import im.vector.app.core.extensions.exhaustive import im.vector.app.core.intent.getFilenameFromUri import im.vector.app.core.platform.OnBackPressed import im.vector.app.core.platform.VectorBaseFragment @@ -97,12 +96,12 @@ class SpaceSettingsFragment @Inject constructor( viewModel.observeViewEvents { when (it) { is RoomSettingsViewEvents.Failure -> showFailure(it.throwable) - RoomSettingsViewEvents.Success -> showSuccess() - RoomSettingsViewEvents.GoBack -> { + RoomSettingsViewEvents.Success -> showSuccess() + RoomSettingsViewEvents.GoBack -> { ignoreChanges = true vectorBaseActivity.onBackPressed() } - }.exhaustive + } } } @@ -245,10 +244,10 @@ class SpaceSettingsFragment @Inject constructor( override fun onAvatarDelete() { withState(viewModel) { when (it.avatarAction) { - RoomSettingsViewState.AvatarAction.None -> { + RoomSettingsViewState.AvatarAction.None -> { viewModel.handle(RoomSettingsAction.SetAvatarAction(RoomSettingsViewState.AvatarAction.DeleteAvatar)) } - RoomSettingsViewState.AvatarAction.DeleteAvatar -> { + RoomSettingsViewState.AvatarAction.DeleteAvatar -> { /* Should not happen */ } is RoomSettingsViewState.AvatarAction.UpdateAvatar -> { diff --git a/vector/src/main/java/im/vector/app/features/spaces/people/SpacePeopleActivity.kt b/vector/src/main/java/im/vector/app/features/spaces/people/SpacePeopleActivity.kt index f5832a8547..23a76b4b68 100644 --- a/vector/src/main/java/im/vector/app/features/spaces/people/SpacePeopleActivity.kt +++ b/vector/src/main/java/im/vector/app/features/spaces/people/SpacePeopleActivity.kt @@ -24,6 +24,7 @@ import androidx.core.view.isVisible import androidx.lifecycle.lifecycleScope import com.airbnb.mvrx.Mavericks import dagger.hilt.android.AndroidEntryPoint +import im.vector.app.R import im.vector.app.core.extensions.hideKeyboard import im.vector.app.core.extensions.replaceFragment import im.vector.app.core.platform.GenericIdArgs @@ -79,7 +80,7 @@ class SpacePeopleActivity : VectorBaseActivity() { is SpacePeopleSharedAction.NavigateToRoom -> navigateToRooms(sharedAction) SpacePeopleSharedAction.HideModalLoading -> hideWaitingView() SpacePeopleSharedAction.ShowModalLoading -> { - showWaitingView() + showWaitingView(getString(R.string.please_wait)) } is SpacePeopleSharedAction.NavigateToInvite -> { ShareSpaceBottomSheet.show(supportFragmentManager, sharedAction.spaceId) diff --git a/vector/src/main/java/im/vector/app/features/spaces/people/SpacePeopleViewModel.kt b/vector/src/main/java/im/vector/app/features/spaces/people/SpacePeopleViewModel.kt index 55d1dbe61e..988f2214c9 100644 --- a/vector/src/main/java/im/vector/app/features/spaces/people/SpacePeopleViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/spaces/people/SpacePeopleViewModel.kt @@ -25,12 +25,14 @@ import dagger.assisted.AssistedFactory import dagger.assisted.AssistedInject import im.vector.app.core.di.MavericksAssistedViewModelFactory import im.vector.app.core.di.hiltMavericksViewModelFactory -import im.vector.app.core.extensions.exhaustive import im.vector.app.core.platform.VectorViewModel +import im.vector.app.features.analytics.AnalyticsTracker +import im.vector.app.features.analytics.plan.CreatedRoom import im.vector.app.features.raw.wellknown.getElementWellknown import im.vector.app.features.raw.wellknown.isE2EByDefault import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.launch +import org.matrix.android.sdk.api.extensions.orFalse import org.matrix.android.sdk.api.raw.RawService import org.matrix.android.sdk.api.session.Session import org.matrix.android.sdk.api.session.room.model.create.CreateRoomParams @@ -38,7 +40,8 @@ import org.matrix.android.sdk.api.session.room.model.create.CreateRoomParams class SpacePeopleViewModel @AssistedInject constructor( @Assisted val initialState: SpacePeopleViewState, private val rawService: RawService, - private val session: Session + private val session: Session, + private val analyticsTracker: AnalyticsTracker ) : VectorViewModel(initialState) { @AssistedFactory @@ -52,7 +55,7 @@ class SpacePeopleViewModel @AssistedInject constructor( when (action) { is SpacePeopleViewAction.ChatWith -> handleChatWith(action) SpacePeopleViewAction.InviteToSpace -> handleInviteToSpace() - }.exhaustive + } } private fun handleInviteToSpace() { @@ -62,7 +65,7 @@ class SpacePeopleViewModel @AssistedInject constructor( private fun handleChatWith(action: SpacePeopleViewAction.ChatWith) { val otherUserId = action.member.userId if (otherUserId == session.myUserId) return - val existingRoomId = session.getExistingDirectRoomWithUser(otherUserId) + val existingRoomId = session.roomService().getExistingDirectRoomWithUser(otherUserId) if (existingRoomId != null) { // just open it _viewEvents.post(SpacePeopleViewEvents.OpenRoom(existingRoomId)) @@ -83,7 +86,8 @@ class SpacePeopleViewModel @AssistedInject constructor( } try { - val roomId = session.createRoom(roomParams) + val roomId = session.roomService().createRoom(roomParams) + analyticsTracker.capture(CreatedRoom(isDM = roomParams.isDirect.orFalse())) _viewEvents.post(SpacePeopleViewEvents.OpenRoom(roomId)) setState { copy(createAndInviteState = Success(roomId)) } } catch (failure: Throwable) { diff --git a/vector/src/main/java/im/vector/app/features/spaces/preview/SpacePreviewFragment.kt b/vector/src/main/java/im/vector/app/features/spaces/preview/SpacePreviewFragment.kt index e97dab1d86..271b00c707 100644 --- a/vector/src/main/java/im/vector/app/features/spaces/preview/SpacePreviewFragment.kt +++ b/vector/src/main/java/im/vector/app/features/spaces/preview/SpacePreviewFragment.kt @@ -105,7 +105,7 @@ class SpacePreviewFragment @Inject constructor( views.spacePreviewAcceptInviteButton.isEnabled = false views.spacePreviewDeclineInviteButton.isEnabled = false } - is Fail -> { + is Fail -> { views.spacePreviewPeekingProgress.isVisible = false views.spacePreviewButtonBar.isVisible = false } @@ -127,10 +127,10 @@ class SpacePreviewFragment @Inject constructor( private fun handleViewEvents(viewEvents: SpacePreviewViewEvents) { when (viewEvents) { - SpacePreviewViewEvents.Dismiss -> { + SpacePreviewViewEvents.Dismiss -> { sharedActionViewModel.post(SpacePreviewSharedAction.DismissAction) } - SpacePreviewViewEvents.JoinSuccess -> { + SpacePreviewViewEvents.JoinSuccess -> { sharedActionViewModel.post(SpacePreviewSharedAction.HideModalLoading) sharedActionViewModel.post(SpacePreviewSharedAction.DismissAction) } diff --git a/vector/src/main/java/im/vector/app/features/spaces/preview/SpacePreviewState.kt b/vector/src/main/java/im/vector/app/features/spaces/preview/SpacePreviewState.kt index 14f9a45d68..ca6a472985 100644 --- a/vector/src/main/java/im/vector/app/features/spaces/preview/SpacePreviewState.kt +++ b/vector/src/main/java/im/vector/app/features/spaces/preview/SpacePreviewState.kt @@ -22,8 +22,8 @@ import com.airbnb.mvrx.Uninitialized data class SpacePreviewState( val idOrAlias: String, - val name: String? = null, - val topic: String? = null, + val name: String? = null, + val topic: String? = null, val avatarUrl: String? = null, val spaceInfo: Async = Uninitialized, val childInfoList: Async> = Uninitialized, diff --git a/vector/src/main/java/im/vector/app/features/spaces/preview/SpacePreviewViewModel.kt b/vector/src/main/java/im/vector/app/features/spaces/preview/SpacePreviewViewModel.kt index 8d34ad94d8..888f4a65b5 100644 --- a/vector/src/main/java/im/vector/app/features/spaces/preview/SpacePreviewViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/spaces/preview/SpacePreviewViewModel.kt @@ -16,7 +16,6 @@ package im.vector.app.features.spaces.preview -import androidx.lifecycle.viewModelScope import com.airbnb.mvrx.Fail import com.airbnb.mvrx.Loading import com.airbnb.mvrx.MavericksViewModelFactory @@ -32,11 +31,12 @@ import im.vector.app.core.platform.VectorViewModel import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.launch import org.matrix.android.sdk.api.session.Session +import org.matrix.android.sdk.api.session.getRoomSummary import org.matrix.android.sdk.api.session.room.model.RoomType import org.matrix.android.sdk.api.session.room.peeking.PeekResult import org.matrix.android.sdk.api.session.space.JoinSpaceResult -import org.matrix.android.sdk.internal.session.space.peeking.SpacePeekResult -import org.matrix.android.sdk.internal.session.space.peeking.SpaceSubChildPeekResult +import org.matrix.android.sdk.api.session.space.peeking.SpacePeekResult +import org.matrix.android.sdk.api.session.space.peeking.SpaceSubChildPeekResult import timber.log.Timber class SpacePreviewViewModel @AssistedInject constructor( @@ -65,8 +65,8 @@ class SpacePreviewViewModel @AssistedInject constructor( override fun handle(action: SpacePreviewViewAction) { when (action) { - SpacePreviewViewAction.ViewReady -> handleReady() - SpacePreviewViewAction.AcceptInvite -> handleAcceptInvite() + SpacePreviewViewAction.ViewReady -> handleReady() + SpacePreviewViewAction.AcceptInvite -> handleAcceptInvite() SpacePreviewViewAction.DismissInvite -> handleDismissInvite() } } @@ -104,7 +104,7 @@ class SpacePreviewViewModel @AssistedInject constructor( // For now we don't handle partial success, it's just success _viewEvents.post(SpacePreviewViewEvents.JoinSuccess) } - is JoinSpaceResult.Fail -> { + is JoinSpaceResult.Fail -> { _viewEvents.post(SpacePreviewViewEvents.JoinFailure(errorFormatter.toHumanReadable(joinResult.error))) } } diff --git a/vector/src/main/java/im/vector/app/features/spaces/preview/SpaceTabView.kt b/vector/src/main/java/im/vector/app/features/spaces/preview/SpaceTabView.kt index 41fc8bf6b9..ea28129262 100644 --- a/vector/src/main/java/im/vector/app/features/spaces/preview/SpaceTabView.kt +++ b/vector/src/main/java/im/vector/app/features/spaces/preview/SpaceTabView.kt @@ -24,7 +24,7 @@ import im.vector.app.R class SpaceTabView constructor(context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0) : - LinearLayout(context, attrs, defStyleAttr) { + LinearLayout(context, attrs, defStyleAttr) { constructor(context: Context, attrs: AttributeSet) : this(context, attrs, 0) {} constructor(context: Context) : this(context, null, 0) {} diff --git a/vector/src/main/java/im/vector/app/features/spaces/share/ShareSpaceViewModel.kt b/vector/src/main/java/im/vector/app/features/spaces/share/ShareSpaceViewModel.kt index c624f1ed46..43d69e6fef 100644 --- a/vector/src/main/java/im/vector/app/features/spaces/share/ShareSpaceViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/spaces/share/ShareSpaceViewModel.kt @@ -30,6 +30,8 @@ import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.onEach import org.matrix.android.sdk.api.extensions.orFalse import org.matrix.android.sdk.api.session.Session +import org.matrix.android.sdk.api.session.getRoom +import org.matrix.android.sdk.api.session.getRoomSummary import org.matrix.android.sdk.api.session.room.powerlevels.PowerLevelsHelper class ShareSpaceViewModel @AssistedInject constructor( diff --git a/vector/src/main/java/im/vector/app/features/sync/widget/SyncStateView.kt b/vector/src/main/java/im/vector/app/features/sync/widget/SyncStateView.kt index ce6df67d53..01e933e446 100755 --- a/vector/src/main/java/im/vector/app/features/sync/widget/SyncStateView.kt +++ b/vector/src/main/java/im/vector/app/features/sync/widget/SyncStateView.kt @@ -28,7 +28,7 @@ import org.matrix.android.sdk.api.session.initsync.SyncStatusService import org.matrix.android.sdk.api.session.sync.SyncState class SyncStateView @JvmOverloads constructor(context: Context, attrs: AttributeSet? = null, defStyle: Int = 0) : - LinearLayout(context, attrs, defStyle) { + LinearLayout(context, attrs, defStyle) { private val views: ViewSyncStateBinding diff --git a/vector/src/main/java/im/vector/app/features/terms/ReviewTermsActivity.kt b/vector/src/main/java/im/vector/app/features/terms/ReviewTermsActivity.kt index e6071fdd2a..9a86e550a8 100644 --- a/vector/src/main/java/im/vector/app/features/terms/ReviewTermsActivity.kt +++ b/vector/src/main/java/im/vector/app/features/terms/ReviewTermsActivity.kt @@ -23,7 +23,6 @@ import com.google.android.material.dialog.MaterialAlertDialogBuilder import dagger.hilt.android.AndroidEntryPoint import im.vector.app.R import im.vector.app.core.error.ErrorFormatter -import im.vector.app.core.extensions.exhaustive import im.vector.app.core.extensions.replaceFragment import im.vector.app.core.platform.SimpleFragmentActivity import org.matrix.android.sdk.api.session.terms.TermsService @@ -63,7 +62,7 @@ class ReviewTermsActivity : SimpleFragmentActivity() { setResult(Activity.RESULT_OK) finish() } - }.exhaustive + } } } diff --git a/vector/src/main/java/im/vector/app/features/terms/ReviewTermsFragment.kt b/vector/src/main/java/im/vector/app/features/terms/ReviewTermsFragment.kt index cb76e5b31f..53afbf7a07 100644 --- a/vector/src/main/java/im/vector/app/features/terms/ReviewTermsFragment.kt +++ b/vector/src/main/java/im/vector/app/features/terms/ReviewTermsFragment.kt @@ -29,7 +29,6 @@ import im.vector.app.R import im.vector.app.core.epoxy.onClick import im.vector.app.core.extensions.cleanup import im.vector.app.core.extensions.configureWith -import im.vector.app.core.extensions.exhaustive import im.vector.app.core.platform.VectorBaseFragment import im.vector.app.core.utils.openUrlInChromeCustomTab import im.vector.app.databinding.FragmentReviewTermsBinding @@ -70,7 +69,7 @@ class ReviewTermsFragment @Inject constructor( ReviewTermsViewEvents.Success -> { // Handled by the Activity } - }.exhaustive + } } reviewTermsViewModel.handle(ReviewTermsAction.LoadTerms(getString(R.string.resources_language))) diff --git a/vector/src/main/java/im/vector/app/features/terms/ReviewTermsViewModel.kt b/vector/src/main/java/im/vector/app/features/terms/ReviewTermsViewModel.kt index 9932efb11a..3a9b772630 100644 --- a/vector/src/main/java/im/vector/app/features/terms/ReviewTermsViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/terms/ReviewTermsViewModel.kt @@ -24,7 +24,6 @@ import dagger.assisted.AssistedFactory import dagger.assisted.AssistedInject import im.vector.app.core.di.MavericksAssistedViewModelFactory import im.vector.app.core.di.hiltMavericksViewModelFactory -import im.vector.app.core.extensions.exhaustive import im.vector.app.core.platform.VectorViewModel import kotlinx.coroutines.launch import org.matrix.android.sdk.api.session.Session @@ -49,7 +48,7 @@ class ReviewTermsViewModel @AssistedInject constructor( is ReviewTermsAction.LoadTerms -> loadTerms(action) is ReviewTermsAction.MarkTermAsAccepted -> markTermAsAccepted(action) ReviewTermsAction.Accept -> acceptTerms() - }.exhaustive + } } private fun markTermAsAccepted(action: ReviewTermsAction.MarkTermAsAccepted) = withState { state -> @@ -85,7 +84,7 @@ class ReviewTermsViewModel @AssistedInject constructor( viewModelScope.launch { try { - session.agreeToTerms( + session.termsService().agreeToTerms( termsArgs.type, termsArgs.baseURL, agreedUrls, @@ -110,7 +109,7 @@ class ReviewTermsViewModel @AssistedInject constructor( viewModelScope.launch { try { - val data = session.getTerms(termsArgs.type, termsArgs.baseURL) + val data = session.termsService().getTerms(termsArgs.type, termsArgs.baseURL) val terms = data.serverResponse.getLocalizedTerms(action.preferredLanguageCode).map { Term(it.localizedUrl ?: "", it.localizedName ?: "", diff --git a/vector/src/main/java/im/vector/app/features/terms/TermsController.kt b/vector/src/main/java/im/vector/app/features/terms/TermsController.kt index 6109e9abc8..10238829b3 100644 --- a/vector/src/main/java/im/vector/app/features/terms/TermsController.kt +++ b/vector/src/main/java/im/vector/app/features/terms/TermsController.kt @@ -17,8 +17,9 @@ package im.vector.app.features.terms import com.airbnb.epoxy.TypedEpoxyController import com.airbnb.mvrx.Fail -import com.airbnb.mvrx.Incomplete +import com.airbnb.mvrx.Loading import com.airbnb.mvrx.Success +import com.airbnb.mvrx.Uninitialized import im.vector.app.R import im.vector.app.core.epoxy.errorWithRetryItem import im.vector.app.core.epoxy.loadingItem @@ -38,19 +39,20 @@ class TermsController @Inject constructor( val host = this when (data.termsList) { - is Incomplete -> { + Uninitialized, + is Loading -> { loadingItem { id("loading") } } - is Fail -> { + is Fail -> { errorWithRetryItem { id("errorRetry") text(host.errorFormatter.toHumanReadable(data.termsList.error)) listener { host.listener?.retry() } } } - is Success -> buildTerms(data.termsList.invoke()) + is Success -> buildTerms(data.termsList.invoke()) } } @@ -67,7 +69,7 @@ class TermsController @Inject constructor( description(host.description) checked(term.accepted) - clickListener { host.listener?.review(term) } + clickListener { host.listener?.review(term) } checkChangeListener { _, isChecked -> host.listener?.setChecked(term, isChecked) } diff --git a/vector/src/main/java/im/vector/app/features/usercode/UserCodeActivity.kt b/vector/src/main/java/im/vector/app/features/usercode/UserCodeActivity.kt index 356893aee2..9e0aa15297 100644 --- a/vector/src/main/java/im/vector/app/features/usercode/UserCodeActivity.kt +++ b/vector/src/main/java/im/vector/app/features/usercode/UserCodeActivity.kt @@ -30,7 +30,6 @@ import com.airbnb.mvrx.viewModel import com.airbnb.mvrx.withState import dagger.hilt.android.AndroidEntryPoint import im.vector.app.R -import im.vector.app.core.extensions.exhaustive import im.vector.app.core.extensions.replaceFragment import im.vector.app.core.platform.VectorBaseActivity import im.vector.app.core.utils.onPermissionDeniedSnackbar @@ -127,7 +126,7 @@ class UserCodeActivity : VectorBaseActivity(), Toast.makeText(this, R.string.qr_code_not_scanned, Toast.LENGTH_SHORT).show() finish() } - }.exhaustive + } } } @@ -153,7 +152,7 @@ class UserCodeActivity : VectorBaseActivity(), UserCodeState.Mode.SHOW -> super.onBackPressed() is UserCodeState.Mode.RESULT, UserCodeState.Mode.SCAN -> sharedViewModel.handle(UserCodeActions.SwitchMode(UserCodeState.Mode.SHOW)) - }.exhaustive + } } companion object { diff --git a/vector/src/main/java/im/vector/app/features/usercode/UserCodeSharedViewModel.kt b/vector/src/main/java/im/vector/app/features/usercode/UserCodeSharedViewModel.kt index 64bcf9cead..02955cf2b3 100644 --- a/vector/src/main/java/im/vector/app/features/usercode/UserCodeSharedViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/usercode/UserCodeSharedViewModel.kt @@ -31,6 +31,7 @@ import kotlinx.coroutines.launch import org.matrix.android.sdk.api.extensions.tryOrNull import org.matrix.android.sdk.api.raw.RawService import org.matrix.android.sdk.api.session.Session +import org.matrix.android.sdk.api.session.getUser import org.matrix.android.sdk.api.session.permalinks.PermalinkData import org.matrix.android.sdk.api.session.permalinks.PermalinkParser import org.matrix.android.sdk.api.session.user.model.User @@ -62,12 +63,12 @@ class UserCodeSharedViewModel @AssistedInject constructor( override fun handle(action: UserCodeActions) { when (action) { - UserCodeActions.DismissAction -> _viewEvents.post(UserCodeShareViewEvents.Dismiss) - is UserCodeActions.SwitchMode -> setState { copy(mode = action.mode) } - is UserCodeActions.DecodedQRCode -> handleQrCodeDecoded(action) - is UserCodeActions.StartChattingWithUser -> handleStartChatting(action) + UserCodeActions.DismissAction -> _viewEvents.post(UserCodeShareViewEvents.Dismiss) + is UserCodeActions.SwitchMode -> setState { copy(mode = action.mode) } + is UserCodeActions.DecodedQRCode -> handleQrCodeDecoded(action) + is UserCodeActions.StartChattingWithUser -> handleStartChatting(action) is UserCodeActions.CameraPermissionNotGranted -> _viewEvents.post(UserCodeShareViewEvents.CameraPermissionNotGranted(action.deniedPermanently)) - UserCodeActions.ShareByText -> handleShareByText() + UserCodeActions.ShareByText -> handleShareByText() } } @@ -110,12 +111,12 @@ class UserCodeSharedViewModel @AssistedInject constructor( _viewEvents.post(UserCodeShareViewEvents.ShowWaitingScreen) viewModelScope.launch(Dispatchers.IO) { when (linkedId) { - is PermalinkData.RoomLink -> { + is PermalinkData.RoomLink -> { // not yet supported _viewEvents.post(UserCodeShareViewEvents.ToastMessage(stringProvider.getString(R.string.not_implemented))) } - is PermalinkData.UserLink -> { - val user = tryOrNull { session.resolveUser(linkedId.userId) } + is PermalinkData.UserLink -> { + val user = tryOrNull { session.userService().resolveUser(linkedId.userId) } // Create raw Uxid in case the user is not searchable ?: User(linkedId.userId, null, null) @@ -125,14 +126,15 @@ class UserCodeSharedViewModel @AssistedInject constructor( ) } } - is PermalinkData.GroupLink -> { + is PermalinkData.GroupLink -> { // not yet supported _viewEvents.post(UserCodeShareViewEvents.ToastMessage(stringProvider.getString(R.string.not_implemented))) } - is PermalinkData.FallbackLink -> { + is PermalinkData.FallbackLink -> { // not yet supported _viewEvents.post(UserCodeShareViewEvents.ToastMessage(stringProvider.getString(R.string.not_implemented))) } + is PermalinkData.RoomEmailInviteLink -> Unit } _viewEvents.post(UserCodeShareViewEvents.HideWaitingScreen) } diff --git a/vector/src/main/java/im/vector/app/features/userdirectory/UserListViewModel.kt b/vector/src/main/java/im/vector/app/features/userdirectory/UserListViewModel.kt index 61f8bc35f3..291153ef2b 100644 --- a/vector/src/main/java/im/vector/app/features/userdirectory/UserListViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/userdirectory/UserListViewModel.kt @@ -26,7 +26,6 @@ import dagger.assisted.AssistedInject import im.vector.app.R import im.vector.app.core.di.MavericksAssistedViewModelFactory import im.vector.app.core.di.hiltMavericksViewModelFactory -import im.vector.app.core.extensions.exhaustive import im.vector.app.core.extensions.isEmail import im.vector.app.core.extensions.toggle import im.vector.app.core.platform.VectorViewModel @@ -46,7 +45,6 @@ import org.matrix.android.sdk.api.session.Session import org.matrix.android.sdk.api.session.identity.IdentityServiceError import org.matrix.android.sdk.api.session.identity.IdentityServiceListener import org.matrix.android.sdk.api.session.identity.ThreePid -import org.matrix.android.sdk.api.session.profile.ProfileService import org.matrix.android.sdk.api.session.user.model.User import org.matrix.android.sdk.api.util.toMatrixItem @@ -113,7 +111,7 @@ class UserListViewModel @AssistedInject constructor( UserListAction.UserConsentRequest -> handleUserConsentRequest() is UserListAction.UpdateUserConsent -> handleISUpdateConsent(action) UserListAction.Resumed -> handleResumed() - }.exhaustive + } } private fun handleUserConsentRequest() { @@ -193,7 +191,7 @@ class UserListViewModel @AssistedInject constructor( knownUsersSearch .sample(300) .flatMapLatest { search -> - session.getPagedUsersLive(search, state.excludedUserIds).asFlow() + session.userService().getPagedUsersLive(search, state.excludedUserIds).asFlow() } .execute { copy(knownUsers = it) @@ -214,14 +212,10 @@ class UserListViewModel @AssistedInject constructor( ThreePidUser(email = search, user = null) } else { try { - val json = session.getProfile(foundThreePid.matrixId) + val user = tryOrNull { session.profileService().getProfileAsUser(foundThreePid.matrixId) } ?: User(foundThreePid.matrixId) ThreePidUser( email = search, - user = User( - userId = foundThreePid.matrixId, - displayName = json[ProfileService.DISPLAY_NAME_KEY] as? String, - avatarUrl = json[ProfileService.AVATAR_URL_KEY] as? String - ) + user = user ) } catch (failure: Throwable) { ThreePidUser(email = search, user = User(foundThreePid.matrixId)) @@ -238,14 +232,15 @@ class UserListViewModel @AssistedInject constructor( emptyList() } else { val searchResult = session + .userService() .searchUsersDirectory(search, 50, state.excludedUserIds.orEmpty()) .sortedBy { it.toMatrixItem().firstLetterOfDisplayName() } val userProfile = if (MatrixPatterns.isUserId(search)) { - val json = tryOrNull { session.getProfile(search) } + val user = tryOrNull { session.profileService().getProfileAsUser(search) } User( userId = search, - displayName = json?.get(ProfileService.DISPLAY_NAME_KEY) as? String, - avatarUrl = json?.get(ProfileService.AVATAR_URL_KEY) as? String + displayName = user?.displayName, + avatarUrl = user?.avatarUrl ) } else { null diff --git a/vector/src/main/java/im/vector/app/features/voice/AbstractVoiceRecorder.kt b/vector/src/main/java/im/vector/app/features/voice/AbstractVoiceRecorder.kt index c07dde5aeb..14bf09c6c4 100644 --- a/vector/src/main/java/im/vector/app/features/voice/AbstractVoiceRecorder.kt +++ b/vector/src/main/java/im/vector/app/features/voice/AbstractVoiceRecorder.kt @@ -21,7 +21,7 @@ import android.media.MediaRecorder import android.net.Uri import android.os.Build import org.matrix.android.sdk.api.session.content.ContentAttachmentData -import org.matrix.android.sdk.internal.util.md5 +import org.matrix.android.sdk.api.util.md5 import java.io.File import java.io.FileOutputStream import java.util.UUID diff --git a/vector/src/main/java/im/vector/app/features/voice/AudioWaveformView.kt b/vector/src/main/java/im/vector/app/features/voice/AudioWaveformView.kt new file mode 100644 index 0000000000..32f30fe458 --- /dev/null +++ b/vector/src/main/java/im/vector/app/features/voice/AudioWaveformView.kt @@ -0,0 +1,201 @@ +/* + * 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.voice + +import android.content.Context +import android.content.res.Resources +import android.graphics.Canvas +import android.graphics.Paint +import android.util.AttributeSet +import android.view.View +import im.vector.app.R +import kotlin.math.max +import kotlin.random.Random + +class AudioWaveformView @JvmOverloads constructor( + context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0 +) : View(context, attrs, defStyleAttr) { + + private enum class Alignment(var value: Int) { + CENTER(0), + BOTTOM(1), + TOP(2) + } + + private enum class Flow(var value: Int) { + LTR(0), + RTL(1) + } + + data class FFT(val value: Float, var color: Int) + + private fun Int.dp() = this * Resources.getSystem().displayMetrics.density + + // Configuration fields + private var alignment = Alignment.CENTER + private var flow = Flow.LTR + private var verticalPadding = 4.dp() + private var horizontalPadding = 4.dp() + private var barWidth = 2.dp() + private var barSpace = 1.dp() + private var barMinHeight = 1.dp() + private var isBarRounded = true + + private val rawFftList = mutableListOf() + private var visibleBarHeights = mutableListOf() + + private val barPaint = Paint() + + init { + attrs?.let { + context + .theme + .obtainStyledAttributes( + attrs, + R.styleable.AudioWaveformView, + 0, + 0 + ) + .apply { + alignment = Alignment.values().find { it.value == getInt(R.styleable.AudioWaveformView_alignment, alignment.value) }!! + flow = Flow.values().find { it.value == getInt(R.styleable.AudioWaveformView_flow, alignment.value) }!! + verticalPadding = getDimension(R.styleable.AudioWaveformView_verticalPadding, verticalPadding) + horizontalPadding = getDimension(R.styleable.AudioWaveformView_horizontalPadding, horizontalPadding) + barWidth = getDimension(R.styleable.AudioWaveformView_barWidth, barWidth) + barSpace = getDimension(R.styleable.AudioWaveformView_barSpace, barSpace) + barMinHeight = getDimension(R.styleable.AudioWaveformView_barMinHeight, barMinHeight) + isBarRounded = getBoolean(R.styleable.AudioWaveformView_isBarRounded, isBarRounded) + setWillNotDraw(false) + barPaint.isAntiAlias = true + } + .apply { recycle() } + .also { + barPaint.strokeWidth = barWidth + barPaint.strokeCap = if (isBarRounded) Paint.Cap.ROUND else Paint.Cap.BUTT + } + } + } + + fun initialize(fftList: List) { + handleNewFftList(fftList) + invalidate() + } + + fun add(fft: FFT) { + handleNewFftList(listOf(fft)) + invalidate() + } + + fun summarize() { + if (rawFftList.isEmpty()) return + + val maxVisibleBarCount = getMaxVisibleBarCount() + val summarizedFftList = rawFftList.summarize(maxVisibleBarCount) + clear() + handleNewFftList(summarizedFftList) + invalidate() + } + + fun updateColors(limitPercentage: Float, colorBefore: Int, colorAfter: Int) { + val size = visibleBarHeights.size + val limitIndex = (size * limitPercentage).toInt() + visibleBarHeights.forEachIndexed { index, fft -> + fft.color = if (index < limitIndex) { + colorBefore + } else { + colorAfter + } + } + invalidate() + } + + fun clear() { + rawFftList.clear() + visibleBarHeights.clear() + } + + private fun List.summarize(target: Int): List { + flow = Flow.LTR + val result = mutableListOf() + if (size <= target) { + result.addAll(this) + val missingItemCount = target - size + repeat(missingItemCount) { + val index = Random.nextInt(result.size) + result.add(index, result[index]) + } + } else { + val step = (size.toDouble() - 1) / (target - 1) + var index = 0.0 + while (index < size) { + result.add(get(index.toInt())) + index += step + } + } + return result + } + + private fun handleNewFftList(fftList: List) { + val maxVisibleBarCount = getMaxVisibleBarCount() + fftList.forEach { fft -> + rawFftList.add(fft) + val barHeight = max(fft.value / MAX_FFT * (height - verticalPadding * 2), barMinHeight) + visibleBarHeights.add(FFT(barHeight, fft.color)) + if (visibleBarHeights.size > maxVisibleBarCount) { + visibleBarHeights = visibleBarHeights.subList(visibleBarHeights.size - maxVisibleBarCount, visibleBarHeights.size) + } + } + } + + private fun getMaxVisibleBarCount() = ((width - horizontalPadding * 2) / (barWidth + barSpace)).toInt() + + private fun drawBars(canvas: Canvas) { + var currentX = horizontalPadding + val flowableBarHeights = if (flow == Flow.LTR) visibleBarHeights else visibleBarHeights.reversed() + + flowableBarHeights.forEach { + barPaint.color = it.color + when (alignment) { + Alignment.BOTTOM -> { + val startY = height - verticalPadding + val stopY = startY - it.value + canvas.drawLine(currentX, startY, currentX, stopY, barPaint) + } + Alignment.CENTER -> { + val startY = (height - it.value) / 2 + val stopY = startY + it.value + canvas.drawLine(currentX, startY, currentX, stopY, barPaint) + } + Alignment.TOP -> { + val startY = verticalPadding + val stopY = startY + it.value + canvas.drawLine(currentX, startY, currentX, stopY, barPaint) + } + } + currentX += barWidth + barSpace + } + } + + override fun onDraw(canvas: Canvas) { + super.onDraw(canvas) + drawBars(canvas) + } + + companion object { + const val MAX_FFT = 32760 + } +} diff --git a/vector/src/main/java/im/vector/app/features/webview/ConsentWebViewEventListener.kt b/vector/src/main/java/im/vector/app/features/webview/ConsentWebViewEventListener.kt index a3d4902b41..91253383ea 100644 --- a/vector/src/main/java/im/vector/app/features/webview/ConsentWebViewEventListener.kt +++ b/vector/src/main/java/im/vector/app/features/webview/ConsentWebViewEventListener.kt @@ -32,7 +32,7 @@ private const val RIOT_BOT_ID = "@riot-bot:matrix.org" class ConsentWebViewEventListener(activity: VectorBaseActivity<*>, private val session: Session, private val delegate: WebViewEventListener) : - WebViewEventListener by delegate { + WebViewEventListener by delegate { private val safeActivity: VectorBaseActivity<*>? by weak(activity) diff --git a/vector/src/main/java/im/vector/app/features/widgets/WidgetActivity.kt b/vector/src/main/java/im/vector/app/features/widgets/WidgetActivity.kt index 963bd9521c..77ec4c5b06 100644 --- a/vector/src/main/java/im/vector/app/features/widgets/WidgetActivity.kt +++ b/vector/src/main/java/im/vector/app/features/widgets/WidgetActivity.kt @@ -80,6 +80,7 @@ class WidgetActivity : VectorBaseActivity() { viewModel.observeViewEvents { when (it) { is WidgetViewEvents.Close -> handleClose(it) + else -> Unit } } diff --git a/vector/src/main/java/im/vector/app/features/widgets/WidgetFragment.kt b/vector/src/main/java/im/vector/app/features/widgets/WidgetFragment.kt index 8fa9e07848..dbd63186b6 100644 --- a/vector/src/main/java/im/vector/app/features/widgets/WidgetFragment.kt +++ b/vector/src/main/java/im/vector/app/features/widgets/WidgetFragment.kt @@ -29,7 +29,6 @@ import android.view.ViewGroup import androidx.core.view.isInvisible import androidx.core.view.isVisible import com.airbnb.mvrx.Fail -import com.airbnb.mvrx.Incomplete import com.airbnb.mvrx.Loading import com.airbnb.mvrx.Success import com.airbnb.mvrx.Uninitialized @@ -87,6 +86,7 @@ class WidgetFragment @Inject constructor() : is WidgetViewEvents.OnURLFormatted -> loadFormattedUrl(it) is WidgetViewEvents.DisplayIntegrationManager -> displayIntegrationManager(it) is WidgetViewEvents.Failure -> displayErrorDialog(it.throwable) + is WidgetViewEvents.Close -> Unit } } viewModel.handle(WidgetAction.LoadFormattedUrl) @@ -192,13 +192,14 @@ class WidgetFragment @Inject constructor() : override fun invalidate() = withState(viewModel) { state -> Timber.v("Invalidate state: $state") when (state.formattedURL) { - is Incomplete -> { + Uninitialized, + is Loading -> { setStateError(null) views.widgetWebView.isInvisible = true views.widgetProgressBar.isIndeterminate = true views.widgetProgressBar.isVisible = true } - is Success -> { + is Success -> { setStateError(null) when (state.webviewLoadedUrl) { Uninitialized -> { @@ -221,7 +222,7 @@ class WidgetFragment @Inject constructor() : } } } - is Fail -> { + is Fail -> { // we need to show Error views.widgetWebView.isInvisible = true views.widgetProgressBar.isVisible = false diff --git a/vector/src/main/java/im/vector/app/features/widgets/WidgetPostAPIHandler.kt b/vector/src/main/java/im/vector/app/features/widgets/WidgetPostAPIHandler.kt index cdffbd5411..e616f8f73f 100644 --- a/vector/src/main/java/im/vector/app/features/widgets/WidgetPostAPIHandler.kt +++ b/vector/src/main/java/im/vector/app/features/widgets/WidgetPostAPIHandler.kt @@ -33,14 +33,13 @@ import org.matrix.android.sdk.api.session.events.model.Event import org.matrix.android.sdk.api.session.events.model.EventType import org.matrix.android.sdk.api.session.events.model.toContent import org.matrix.android.sdk.api.session.events.model.toModel +import org.matrix.android.sdk.api.session.getRoom import org.matrix.android.sdk.api.session.room.model.Membership import org.matrix.android.sdk.api.session.room.model.PowerLevelsContent import org.matrix.android.sdk.api.session.room.powerlevels.PowerLevelsHelper import org.matrix.android.sdk.api.session.widgets.WidgetPostAPIMediator import org.matrix.android.sdk.api.util.JsonDict import timber.log.Timber -import java.util.ArrayList -import java.util.HashMap class WidgetPostAPIHandler @AssistedInject constructor(@Assisted private val roomId: String, private val stringProvider: StringProvider, diff --git a/vector/src/main/java/im/vector/app/features/widgets/WidgetViewModel.kt b/vector/src/main/java/im/vector/app/features/widgets/WidgetViewModel.kt index 20fae6e31a..c7b1429304 100644 --- a/vector/src/main/java/im/vector/app/features/widgets/WidgetViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/widgets/WidgetViewModel.kt @@ -37,6 +37,7 @@ import org.matrix.android.sdk.api.session.Session import org.matrix.android.sdk.api.session.events.model.Content import org.matrix.android.sdk.api.session.events.model.EventType import org.matrix.android.sdk.api.session.events.model.toModel +import org.matrix.android.sdk.api.session.getRoom import org.matrix.android.sdk.api.session.integrationmanager.IntegrationManagerService import org.matrix.android.sdk.api.session.room.model.PowerLevelsContent import org.matrix.android.sdk.api.session.room.powerlevels.PowerLevelsHelper @@ -51,7 +52,7 @@ class WidgetViewModel @AssistedInject constructor(@Assisted val initialState: Wi widgetPostAPIHandlerFactory: WidgetPostAPIHandler.Factory, private val stringProvider: StringProvider, private val session: Session) : - VectorViewModel(initialState), + VectorViewModel(initialState), WidgetPostAPIHandler.NavigationCallback, IntegrationManagerService.Listener { diff --git a/vector/src/main/java/im/vector/app/features/widgets/permissions/RoomWidgetPermissionViewModel.kt b/vector/src/main/java/im/vector/app/features/widgets/permissions/RoomWidgetPermissionViewModel.kt index f29e6d1928..7ab2cf174d 100644 --- a/vector/src/main/java/im/vector/app/features/widgets/permissions/RoomWidgetPermissionViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/widgets/permissions/RoomWidgetPermissionViewModel.kt @@ -37,7 +37,7 @@ import java.net.URL class RoomWidgetPermissionViewModel @AssistedInject constructor(@Assisted val initialState: RoomWidgetPermissionViewState, private val session: Session) : - VectorViewModel(initialState) { + VectorViewModel(initialState) { private val widgetService = session.widgetService() private val integrationManagerService = session.integrationManagerService() @@ -93,6 +93,7 @@ class RoomWidgetPermissionViewModel @AssistedInject constructor(@Assisted val in when (action) { RoomWidgetPermissionActions.AllowWidget -> handleAllowWidget() RoomWidgetPermissionActions.BlockWidget -> handleRevokeWidget() + RoomWidgetPermissionActions.DoClose -> Unit } } diff --git a/vector/src/main/java/im/vector/app/features/workers/signout/ServerBackupStatusViewModel.kt b/vector/src/main/java/im/vector/app/features/workers/signout/ServerBackupStatusViewModel.kt index 8fb5b27376..1c55145e16 100644 --- a/vector/src/main/java/im/vector/app/features/workers/signout/ServerBackupStatusViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/workers/signout/ServerBackupStatusViewModel.kt @@ -87,7 +87,7 @@ class ServerBackupStatusViewModel @AssistedInject constructor(@Assisted initialS val liveCrossSigningPrivateKeys = session.flow().liveCrossSigningPrivateKeys() combine(liveUserAccountData, liveCrossSigningInfo, keyBackupFlow, liveCrossSigningPrivateKeys) { _, crossSigningInfo, keyBackupState, pInfo -> // first check if 4S is already setup - if (session.sharedSecretStorageService.isRecoverySetup()) { + if (session.sharedSecretStorageService().isRecoverySetup()) { // 4S is already setup sp we should not display anything return@combine when (keyBackupState) { KeysBackupState.BackingUp -> BannerState.BackingUp diff --git a/vector/src/main/java/im/vector/app/features/workers/signout/SignoutCheckViewModel.kt b/vector/src/main/java/im/vector/app/features/workers/signout/SignoutCheckViewModel.kt index 4daaef6fe1..79df62d7fb 100644 --- a/vector/src/main/java/im/vector/app/features/workers/signout/SignoutCheckViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/workers/signout/SignoutCheckViewModel.kt @@ -28,7 +28,6 @@ import dagger.assisted.AssistedFactory import dagger.assisted.AssistedInject import im.vector.app.core.di.MavericksAssistedViewModelFactory import im.vector.app.core.di.hiltMavericksViewModelFactory -import im.vector.app.core.extensions.exhaustive import im.vector.app.core.platform.EmptyViewEvents import im.vector.app.core.platform.VectorViewModel import im.vector.app.core.platform.VectorViewModelAction @@ -75,7 +74,7 @@ class SignoutCheckViewModel @AssistedInject constructor( session.cryptoService().keysBackupService().addListener(this) session.cryptoService().keysBackupService().checkAndStartKeysBackup() - val quad4SIsSetup = session.sharedSecretStorageService.isRecoverySetup() + val quad4SIsSetup = session.sharedSecretStorageService().isRecoverySetup() val allKeysKnown = session.cryptoService().crossSigningService().allPrivateKeysKnown() val backupState = session.cryptoService().keysBackupService().state setState { @@ -89,7 +88,7 @@ class SignoutCheckViewModel @AssistedInject constructor( session.flow().liveUserAccountData(setOf(MASTER_KEY_SSSS_NAME, USER_SIGNING_KEY_SSSS_NAME, SELF_SIGNING_KEY_SSSS_NAME)) .map { - session.sharedSecretStorageService.isRecoverySetup() + session.sharedSecretStorageService().isRecoverySetup() } .distinctUntilChanged() .execute { @@ -124,7 +123,7 @@ class SignoutCheckViewModel @AssistedInject constructor( copy(hasBeenExportedToFile = Success(true)) } } - }.exhaustive + } } private fun handleExportKeys(action: Actions.ExportKeys) { diff --git a/vector/src/main/res/drawable-hdpi/bg_no_location_map.webp b/vector/src/main/res/drawable-hdpi/bg_no_location_map.webp new file mode 100644 index 0000000000..23a45700f0 Binary files /dev/null and b/vector/src/main/res/drawable-hdpi/bg_no_location_map.webp differ diff --git a/vector/src/main/res/drawable-mdpi/bg_no_location_map.webp b/vector/src/main/res/drawable-mdpi/bg_no_location_map.webp new file mode 100644 index 0000000000..a6130fba78 Binary files /dev/null and b/vector/src/main/res/drawable-mdpi/bg_no_location_map.webp differ diff --git a/vector/src/main/res/drawable-xhdpi/bg_no_location_map.webp b/vector/src/main/res/drawable-xhdpi/bg_no_location_map.webp new file mode 100644 index 0000000000..e908191371 Binary files /dev/null and b/vector/src/main/res/drawable-xhdpi/bg_no_location_map.webp differ diff --git a/vector/src/main/res/drawable-xxhdpi/bg_no_location_map.webp b/vector/src/main/res/drawable-xxhdpi/bg_no_location_map.webp new file mode 100644 index 0000000000..e062178367 Binary files /dev/null and b/vector/src/main/res/drawable-xxhdpi/bg_no_location_map.webp differ diff --git a/vector/src/main/res/drawable-xxxhdpi/bg_no_location_map.webp b/vector/src/main/res/drawable-xxxhdpi/bg_no_location_map.webp new file mode 100644 index 0000000000..8b110d33fe Binary files /dev/null and b/vector/src/main/res/drawable-xxxhdpi/bg_no_location_map.webp differ diff --git a/vector/src/main/res/drawable/bg_seek_bar.xml b/vector/src/main/res/drawable/bg_seek_bar.xml new file mode 100644 index 0000000000..0a33522dfd --- /dev/null +++ b/vector/src/main/res/drawable/bg_seek_bar.xml @@ -0,0 +1,21 @@ + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/vector/src/main/res/drawable/ic_choose_server.xml b/vector/src/main/res/drawable/ic_choose_server.xml new file mode 100644 index 0000000000..26c8e75222 --- /dev/null +++ b/vector/src/main/res/drawable/ic_choose_server.xml @@ -0,0 +1,9 @@ + + + diff --git a/vector/src/main/res/drawable/ic_ems_logo.xml b/vector/src/main/res/drawable/ic_ems_logo.xml new file mode 100644 index 0000000000..68c2aeb190 --- /dev/null +++ b/vector/src/main/res/drawable/ic_ems_logo.xml @@ -0,0 +1,22 @@ + + + + + + diff --git a/vector/src/main/res/drawable/ic_feedback.xml b/vector/src/main/res/drawable/ic_feedback.xml deleted file mode 100644 index e774a8ab5c..0000000000 --- a/vector/src/main/res/drawable/ic_feedback.xml +++ /dev/null @@ -1,14 +0,0 @@ - - - - diff --git a/vector/src/main/res/drawable/ic_headphones.xml b/vector/src/main/res/drawable/ic_headphones.xml deleted file mode 100644 index 86f3d8ab7f..0000000000 --- a/vector/src/main/res/drawable/ic_headphones.xml +++ /dev/null @@ -1,16 +0,0 @@ - - - - diff --git a/vector/src/main/res/drawable/ic_presence_online.xml b/vector/src/main/res/drawable/ic_presence_online.xml index 2184f359b2..e5229de3fd 100644 --- a/vector/src/main/res/drawable/ic_presence_online.xml +++ b/vector/src/main/res/drawable/ic_presence_online.xml @@ -16,7 +16,7 @@ diff --git a/vector/src/main/res/drawable/ic_privacy_policy.xml b/vector/src/main/res/drawable/ic_privacy_policy.xml new file mode 100644 index 0000000000..08c63ba44b --- /dev/null +++ b/vector/src/main/res/drawable/ic_privacy_policy.xml @@ -0,0 +1,10 @@ + + + diff --git a/vector/src/main/res/drawable/ic_secure_backup.xml b/vector/src/main/res/drawable/ic_secure_backup.xml index 899bb8d2ae..78bb71e829 100644 --- a/vector/src/main/res/drawable/ic_secure_backup.xml +++ b/vector/src/main/res/drawable/ic_secure_backup.xml @@ -1,4 +1,5 @@ + android:fillColor="#2E2F32" + tools:fillColor="#FF0000"/> + android:fillColor="#2E2F32" + tools:fillColor="#FF0000"/> + android:fillType="evenOdd" + tools:fillColor="#FF0000"/> diff --git a/vector/src/main/res/drawable/ic_share_screen.xml b/vector/src/main/res/drawable/ic_share_screen.xml new file mode 100644 index 0000000000..5e3caa6987 --- /dev/null +++ b/vector/src/main/res/drawable/ic_share_screen.xml @@ -0,0 +1,10 @@ + + + diff --git a/vector/src/main/res/layout/activity_call_transfer.xml b/vector/src/main/res/layout/activity_call_transfer.xml index c3febc96a7..6bf334e62d 100644 --- a/vector/src/main/res/layout/activity_call_transfer.xml +++ b/vector/src/main/res/layout/activity_call_transfer.xml @@ -17,6 +17,7 @@ @@ -86,4 +87,4 @@ layout="@layout/merge_overlay_waiting_view" /> - \ No newline at end of file + diff --git a/vector/src/main/res/layout/bottom_sheet_call_controls.xml b/vector/src/main/res/layout/bottom_sheet_call_controls.xml index e751ac412a..516dc29fa3 100644 --- a/vector/src/main/res/layout/bottom_sheet_call_controls.xml +++ b/vector/src/main/res/layout/bottom_sheet_call_controls.xml @@ -7,6 +7,15 @@ android:background="?colorSurface" android:orientation="vertical"> + + + + + + + + + + + + + + + +