diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..ae8d2d3 --- /dev/null +++ b/.editorconfig @@ -0,0 +1,441 @@ +root = true + + +[*] +charset = utf-8 +end_of_line = lf +indent_size = 4 +indent_style = tab +insert_final_newline = true +trim_trailing_whitespace = true +max_line_length = 120 +ij_smart_tabs = true +ij_continuation_indent_size = 4 +ij_formatter_off_tag = @formatter:off +ij_formatter_on_tag = @formatter:on +ij_formatter_tags_enabled = true +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_align_types_in_multi_catch = true +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_add_space = false +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_not_wrap_after_single_annotation_in_parameter = 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 = 0 +ij_java_keep_blank_lines_between_package_declaration_and_header = 1 +ij_java_keep_blank_lines_in_code = 1 +ij_java_keep_blank_lines_in_declarations = 1 +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 = false +ij_java_line_comment_add_space = false +ij_java_line_comment_add_space_on_reformat = 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_multi_catch_types_wrap = normal +ij_java_names_count_to_use_import_on_demand = 99 +ij_java_new_line_after_lparen_in_annotation = false +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_annotation = false +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_annotation_eq = 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 + + +[{*.kt,*.kts}] +ij_kotlin_align_in_columns_case_branch = false +ij_kotlin_align_multiline_binary_operation = false +ij_kotlin_align_multiline_extends_list = false +ij_kotlin_align_multiline_method_parentheses = false +ij_kotlin_align_multiline_parameters = false +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 = normal +ij_kotlin_blank_lines_after_class_header = 1 +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_add_space = false +ij_kotlin_block_comment_at_first_column = true +ij_kotlin_call_parameters_new_line_after_left_paren = true +ij_kotlin_call_parameters_right_paren_on_new_line = true +ij_kotlin_call_parameters_wrap = on_every_item +ij_kotlin_catch_on_new_line = false +ij_kotlin_class_annotation_wrap = split_into_lines +ij_kotlin_code_style_defaults = KOTLIN_OFFICIAL +ij_kotlin_continuation_indent_for_chained_calls = false +ij_kotlin_continuation_indent_for_expression_bodies = false +ij_kotlin_continuation_indent_in_argument_lists = false +ij_kotlin_continuation_indent_in_elvis = false +ij_kotlin_continuation_indent_in_if_conditions = false +ij_kotlin_continuation_indent_in_parameter_lists = false +ij_kotlin_continuation_indent_in_supertype_lists = false +ij_kotlin_else_on_new_line = false +ij_kotlin_enum_constants_wrap = on_every_item +ij_kotlin_extends_list_wrap = normal +ij_kotlin_field_annotation_wrap = split_into_lines +ij_kotlin_finally_on_new_line = false +ij_kotlin_if_rparen_on_new_line = true +ij_kotlin_import_nested_classes = true +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 = false +ij_kotlin_lbrace_on_next_line = false +ij_kotlin_line_break_after_multiline_when_entry = false +ij_kotlin_line_comment_add_space = false +ij_kotlin_line_comment_add_space_on_reformat = false +ij_kotlin_line_comment_at_first_column = true +ij_kotlin_method_annotation_wrap = split_into_lines +ij_kotlin_method_call_chain_wrap = on_every_item +ij_kotlin_method_parameters_new_line_after_left_paren = true +ij_kotlin_method_parameters_right_paren_on_new_line = true +ij_kotlin_method_parameters_wrap = on_every_item +ij_kotlin_name_count_to_use_star_import = 2147483647 +ij_kotlin_name_count_to_use_star_import_for_members = 2147483647 +ij_kotlin_parameter_annotation_wrap = normal +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 = false +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 = normal +ij_kotlin_while_on_new_line = false +ij_kotlin_wrap_elvis_expressions = 1 +ij_kotlin_wrap_expression_body_functions = 1 +ij_kotlin_wrap_first_method_in_call_chain = true + + +[*.xml] +ij_xml_align_attributes = false +ij_xml_align_text = false +ij_xml_attribute_wrap = normal +ij_xml_block_comment_add_space = false +ij_xml_block_comment_at_first_column = true +ij_xml_keep_blank_lines = 1 +ij_xml_keep_indents_on_empty_lines = false +ij_xml_keep_line_breaks = false +ij_xml_keep_line_breaks_in_text = false +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 = false +ij_xml_text_wrap = normal +ij_xml_use_custom_settings = true + + +[*.properties] +ij_properties_align_group_field_declarations = false +ij_properties_keep_blank_lines = true +ij_properties_key_value_delimiter = equals +ij_properties_spaces_around_key_value_delimiter = false + + +[{*.markdown,*.md}] +ij_markdown_force_one_space_after_blockquote_symbol = true +ij_markdown_force_one_space_after_header_symbol = true +ij_markdown_force_one_space_after_list_bullet = true +ij_markdown_force_one_space_between_words = true +ij_markdown_insert_quote_arrows_on_wrap = true +ij_markdown_keep_indents_on_empty_lines = false +ij_markdown_keep_line_breaks_inside_text_blocks = true +ij_markdown_max_lines_around_block_elements = 1 +ij_markdown_max_lines_around_header = 1 +ij_markdown_max_lines_between_paragraphs = 1 +ij_markdown_min_lines_around_block_elements = 1 +ij_markdown_min_lines_around_header = 1 +ij_markdown_min_lines_between_paragraphs = 1 +ij_markdown_wrap_text_if_long = true +ij_markdown_wrap_text_inside_blockquotes = true + + +[*.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 + + +[{*.bash,*.sh,*.zsh}] +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 + + +[*.json] +ij_json_array_wrapping = split_into_lines +ij_json_keep_blank_lines_in_code = 0 +ij_json_keep_indents_on_empty_lines = true +ij_json_keep_line_breaks = true +ij_json_keep_trailing_comma = false +ij_json_object_wrapping = split_into_lines +ij_json_property_alignment = do_not_align +ij_json_space_after_colon = true +ij_json_space_after_comma = true +ij_json_space_before_colon = false +ij_json_space_before_comma = false +ij_json_spaces_within_braces = false +ij_json_spaces_within_brackets = false +ij_json_wrap_long_lines = false + + +[.editorconfig] +ij_editorconfig_align_group_field_declarations = true +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 diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..c12e55b --- /dev/null +++ b/.gitignore @@ -0,0 +1,11 @@ +.gradle +build/ + +local.properties + +*.log +.idea/ +.cxx +*.iml + +*.DS_Store diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..32bcb54 --- /dev/null +++ b/LICENSE @@ -0,0 +1,190 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + Copyright 2024 Blink Commerce Private Limited (formerly known as Grofers India Private Limited) + + 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. diff --git a/README.md b/README.md new file mode 100644 index 0000000..8dbec42 --- /dev/null +++ b/README.md @@ -0,0 +1,187 @@ +
+ +![Droid Dex](./assets/logo.png) + +
+ +## Introduction + +Droid Dex is a powerful tool crafted to enhance the performance of your Android applications, ultimately elevating the +user experience. With a focus on addressing key performance issues, it is your solution for addressing prevalent +challenges like Jerky(Janky) Scrolling, Out of Memory errors (OOMs), High Battery Consumption, and instances of +Application Not Responding (ANR). + +It classifies and lets you analyze Android Device Performance across various parameters like: + +| PARAMETER | DESCRIPTION | +|----------------------------------------------------------------------------------------------------------------------------|-----------------------------------------------------| +|
[CPU](./droid-dex/src/main/kotlin/com/blinkit/droiddex/cpu/CpuPerformanceManager.kt)
| Total RAM, Core Count, CPU Frequency | +|
[MEMORY](./droid-dex/src/main/kotlin/com/blinkit/droiddex/memory/MemoryPerformanceManager.kt)
| Heap Limit, Heap Remaining, Available RAM | +|
[NETWORK](./droid-dex/src/main/kotlin/com/blinkit/droiddex/network/NetworkPerformanceManager.kt)
| Bandwidth Strength, Download Speed, Signal Strength | +|
[STORAGE](./droid-dex/src/main/kotlin/com/blinkit/droiddex/storage/StoragePerformanceManager.kt)
| Available Storage | +|
[BATTERY](./droid-dex/src/main/kotlin/com/blinkit/droiddex/battery/BatteryPerformanceManager.kt)
| Percentage Remaining, If Phone is Charging or Not | + +into various [levels](./droid-dex/src/main/kotlin/com/blinkit/droiddex/constants/PerformanceLevel.kt): EXCELLENT, HIGH, +AVERAGE, LOW + +It is a compact library accompanied by extensive in-line documentation, providing users with the opportunity to delve +into the code, comprehend each line thoroughly, and, ideally, contribute to its development. + +## Use Cases + +1. Consider a scenario where background polling of an API is necessary. In this context, the `BATTERY` level becomes a + crucial factor, as frequent polling can significantly drain the device's battery. To address this concern, you can + optimize the process using the following code snippet: + + ```Kotlin + DroidDex.getPerformanceLevelLd(PerformanceClass.BATTERY).observe(this) { + // Adjust the polling time interval + } + ``` + +2. Consider a scenario where you need to tailor the image quality for users based on their devices. In this context, the + `NETWORK` condition plays a crucial role in decision-making, as achieving better image quality typically involves + larger file sizes and increased data transfer. However, `MEMORY` is also a consideration, as higher-quality images + generate heavier bitmaps, consuming more memory. To optimize this process, you can use the following code snippet: + + ```Kotlin + DroidDex.getWeightedPerformanceLevelLd(PerformanceClass.NETWORK to 2F, PerformanceClass.MEMORY to 1F).observe(this) { + // Implement image quality optimization + } + ``` + +## Usage + +Initialize the library in your Application class using the following code snippet: + +```Kotlin +DroidDex.init(this, BuildConfig.DEBUG) +``` + +The first parameter requires the `Application Context`, while the second parameter determines whether the user is in +`Debug Mode`, enabling extensive logging. + +1. To get performance level for single/multiple parameters: + + ```Kotlin + DroidDex.getPerformanceLevel(params) + ``` + + For observing the changes: + + ```Kotlin + DroidDex.getPerformanceLevelLd(params).observe(this) { + } + ``` + + Replace `params` with comma separated list of `Performance Class(es)`. + + Example: + ```Kotlin + DroidDex.getPerformanceLevel(PerformanceClass.CPU, PerformanceClass.MEMORY) + ``` + +2. To get performance level for multiple parameters with unequal weights: + + ```Kotlin + DroidDex.getWeightedPerformanceLevel(params) + ``` + + For observing the changes: + + ```Kotlin + DroidDex.getWeightedPerformanceLevelLd(params).observe(this) { + } + ``` + + Replace `params` with comma separated list of `Performance Classes` to their `Weights`. + + Example: + ```Kotlin + DroidDex.getWeightedPerformanceLevelLd(PerformanceClass.CPU to 2F, PerformanceClass.MEMORY to 1F).observe(this) { + } + ``` + +
+ Example App +
+ +See [Example Project](https://github.com/grofers/droid-dex/tree/main/example) for Further Usage + +## Download + +
+Kotlin DSL + +Add this to your root `settings.gradle.kts` + +```Kotlin +dependencyResolutionManagement { + repositories { + maven { + url = URI("https://maven.pkg.github.com/grofers/*") + credentials { + username = "Blinkit" + password = GITHUB_PERSONAL_ACCESS_TOKEN + } + } + } +} +``` + +And add this dependency to your project level `build.gradle.kts`: + +```Kotlin +dependencies { + implementation("com.blinkit.kits:droid-dex:x.y.z") +} +``` + +
+ +
+Groovy + +Add this to your root `settings.gradle` + +```Groovy +dependencyResolutionManagement { + repositories { + maven { + url "https://maven.pkg.github.com/grofers/*" + credentials { + username = "Blinkit" + password = GITHUB_PERSONAL_ACCESS_TOKEN + } + } + } +} +``` + +And add this dependency to your project level `build.gradle`: + +```Groovy +dependencies { + implementation "com.blinkit.kits:droid-dex:x.y.z" +} +``` + +
+ +## License + +
+Copyright 2024 Blink Commerce Private Limited (formerly known as Grofers India Private Limited)
+
+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.
+
diff --git a/assets/example.png b/assets/example.png new file mode 100644 index 0000000..a0c9fbb Binary files /dev/null and b/assets/example.png differ diff --git a/assets/logo.png b/assets/logo.png new file mode 100644 index 0000000..bf3a80d Binary files /dev/null and b/assets/logo.png differ diff --git a/build.gradle.kts b/build.gradle.kts new file mode 100644 index 0000000..3cf5684 --- /dev/null +++ b/build.gradle.kts @@ -0,0 +1,8 @@ +plugins { + alias(libs.plugins.android.application) apply false + alias(libs.plugins.android.library) apply false + + alias(libs.plugins.jetbrains.kotlin.android) apply false + + alias(libs.plugins.maven.publish) apply false +} diff --git a/droid-dex/.gitignore b/droid-dex/.gitignore new file mode 100644 index 0000000..42afabf --- /dev/null +++ b/droid-dex/.gitignore @@ -0,0 +1 @@ +/build \ No newline at end of file diff --git a/droid-dex/build.gradle.kts b/droid-dex/build.gradle.kts new file mode 100644 index 0000000..5cdd314 --- /dev/null +++ b/droid-dex/build.gradle.kts @@ -0,0 +1,64 @@ +@file:Suppress("UnstableApiUsage") + +import com.vanniktech.maven.publish.AndroidSingleVariantLibrary +import java.net.URI + +plugins { + alias(libs.plugins.android.library) + alias(libs.plugins.jetbrains.kotlin.android) + + alias(libs.plugins.maven.publish) +} + + +publishing { + repositories { + maven { + name = "Blinkit" + url = URI("https://maven.pkg.github.com/grofers/droid-dex") + credentials { + username = "Blinkit" + password = System.getenv("READ_ARTIFACTS_TOKEN") + } + } + } +} + + +mavenPublishing { + configure(AndroidSingleVariantLibrary("release", sourcesJar = true, publishJavadocJar = true)) +} + + +android { + namespace = "com.blinkit.droiddex" + + compileSdk = libs.versions.compileSdk.get().toInt() + + defaultConfig { + minSdk = libs.versions.minSdk.get().toInt() + + consumerProguardFiles("consumer-rules.pro") + } + + buildTypes { release { isMinifyEnabled = false } } + + compileOptions { + sourceCompatibility = JavaVersion.toVersion(libs.versions.java.get()) + targetCompatibility = JavaVersion.toVersion(libs.versions.java.get()) + } + kotlinOptions { jvmTarget = libs.versions.java.get() } +} + + +dependencies { + implementation(libs.timber) + + implementation(libs.bundles.core) + + implementation(libs.kotlinx.coroutines.android) + + implementation(libs.bundles.lifecycle) + + implementation(libs.androidx.core.performance) +} diff --git a/droid-dex/consumer-rules.pro b/droid-dex/consumer-rules.pro new file mode 100644 index 0000000..424633e --- /dev/null +++ b/droid-dex/consumer-rules.pro @@ -0,0 +1 @@ +-keepattributes SourceFile,LineNumberTable diff --git a/droid-dex/src/main/AndroidManifest.xml b/droid-dex/src/main/AndroidManifest.xml new file mode 100644 index 0000000..f2cb9ae --- /dev/null +++ b/droid-dex/src/main/AndroidManifest.xml @@ -0,0 +1,18 @@ + + + + + + + + + + + + + + + + diff --git a/droid-dex/src/main/kotlin/com/blinkit/droiddex/DroidDex.kt b/droid-dex/src/main/kotlin/com/blinkit/droiddex/DroidDex.kt new file mode 100644 index 0000000..3d54c25 --- /dev/null +++ b/droid-dex/src/main/kotlin/com/blinkit/droiddex/DroidDex.kt @@ -0,0 +1,98 @@ +package com.blinkit.droiddex + +import android.content.Context +import androidx.lifecycle.LiveData +import androidx.lifecycle.MutableLiveData +import com.blinkit.droiddex.constants.PerformanceClass +import com.blinkit.droiddex.constants.PerformanceClass.Companion.name +import com.blinkit.droiddex.constants.PerformanceLevel +import com.blinkit.droiddex.factory.factory.PerformanceManagerFactory +import com.blinkit.droiddex.utils.Logger +import com.blinkit.droiddex.utils.getPerformanceLevelLdWithWeights +import com.blinkit.droiddex.utils.getPerformanceLevelWithWeights + +object DroidDex { + + private lateinit var performanceManagerFactory: PerformanceManagerFactory + + private val logger by lazy { Logger() } + + fun init(applicationContext: Context, isInDebugMode: Boolean = false) { + if (::performanceManagerFactory.isInitialized) { + logger.logError(IllegalStateException("Droid Dex is already initialized")) + return + } + logger.isInDebugMode = isInDebugMode + + try { + performanceManagerFactory = PerformanceManagerFactory(applicationContext, isInDebugMode) + } catch (e: Exception) { + logger.logError(e) + } + } + + /** + * @param classes spread list of performance classes + * @return Average PerformanceLevel for the input classes + * + * Steps for getting PerformanceLevel: + * - Check if the class has been initialized or not + * - Get the PerformanceLevel of individual PerformanceClass + * - Ignore the ones for which the PerformanceLevel is UNKNOWN + * - Take average of all these PerformanceClass + */ + fun getPerformanceLevel(@PerformanceClass vararg classes: Int): PerformanceLevel = + getWeightedPerformanceLevel(*classes.map { Pair(it, 1F) }.toTypedArray()) + + /** + * @param classes spread list of performance classes + * @return LiveData of Average PerformanceLevel for the input classes + * + * Steps for getting PerformanceLevel: + * - Check if the class has been initialized or not + * - Get the PerformanceLevel of individual PerformanceClass + * - Ignore the ones for which the PerformanceLevel is UNKNOWN + * - Take average of all these PerformanceClass + */ + fun getPerformanceLevelLd(@PerformanceClass vararg classes: Int): LiveData = + getWeightedPerformanceLevelLd(*classes.map { Pair(it, 1F) }.toTypedArray()) + + /** + * @param classes spread list of performance classes mapped with weight + * @return Weighted Average PerformanceLevel for the input classes + * + * Steps for getting PerformanceLevel: + * - Check if the class has been initialized or not + * - Get the PerformanceLevel of individual PerformanceClass + * - Ignore the ones for which the PerformanceLevel is UNKNOWN + * - Take weighted average of all these PerformanceClass + */ + fun getWeightedPerformanceLevel(vararg classes: Pair<@PerformanceClass Int, Float>): PerformanceLevel = + checkInitialized(*classes.map { it.first }.toIntArray()) ?: getPerformanceLevelWithWeights(classes.map { + Pair(performanceManagerFactory.getPerformanceLevel(it.first), it.second) + }).also { logger.logPerformanceLevelResult(*classes, performanceLevel = it) } + + /** + * @param classes spread list of performance classes mapped with weight + * @return LiveData of Weighted Average PerformanceLevel for the input classes + * + * Steps for getting PerformanceLevel: + * - Check if the class has been initialized or not + * - Get the PerformanceLevel of individual PerformanceClass + * - Ignore the ones for which the PerformanceLevel is UNKNOWN + * - Take weighted average of all these PerformanceClass + */ + fun getWeightedPerformanceLevelLd(vararg classes: Pair<@PerformanceClass Int, Float>): LiveData = + checkInitialized(*classes.map { it.first }.toIntArray())?.let { MutableLiveData(it) } + ?: getPerformanceLevelLdWithWeights(classes.map { + Pair(performanceManagerFactory.getPerformanceLevelLd(it.first), it.second) + }) { logger.logPerformanceLevelResult(*classes, performanceLevel = it) } + + private fun checkInitialized(@PerformanceClass vararg classes: Int): PerformanceLevel? { + if (::performanceManagerFactory.isInitialized) return null + + val classesNames = classes.joinToString(", ") { it.name() } + logger.logError(UninitializedPropertyAccessException("Droid Dex is not initialized for parameters: $classesNames")) + return PerformanceLevel.UNKNOWN + } +} diff --git a/droid-dex/src/main/kotlin/com/blinkit/droiddex/battery/BatteryPerformanceManager.kt b/droid-dex/src/main/kotlin/com/blinkit/droiddex/battery/BatteryPerformanceManager.kt new file mode 100644 index 0000000..53c0980 --- /dev/null +++ b/droid-dex/src/main/kotlin/com/blinkit/droiddex/battery/BatteryPerformanceManager.kt @@ -0,0 +1,50 @@ +package com.blinkit.droiddex.battery + +import android.content.Context +import android.content.Intent +import android.content.IntentFilter +import android.os.BatteryManager +import com.blinkit.droiddex.constants.PerformanceClass +import com.blinkit.droiddex.constants.PerformanceLevel +import com.blinkit.droiddex.factory.base.PerformanceManager +import com.blinkit.droiddex.factory.providers.PerformanceManagerProvider + +internal class BatteryPerformanceManager( + private val applicationContext: Context, isInDebugMode: Boolean +): PerformanceManager(isInDebugMode) { + + override fun getPerformanceClass() = PerformanceClass.BATTERY + + override fun getDelayInSecs() = DELAY_IN_SECS + + override fun measurePerformanceLevel(): PerformanceLevel { + val batteryStatusIntent = + IntentFilter(Intent.ACTION_BATTERY_CHANGED).let { applicationContext.registerReceiver(null, it) } + batteryStatusIntent ?: return PerformanceLevel.UNKNOWN + + val isCharging = batteryStatusIntent.getIntExtra(BatteryManager.EXTRA_STATUS, -1).let { + it == BatteryManager.BATTERY_STATUS_CHARGING || it == BatteryManager.BATTERY_STATUS_FULL + }.also { logDebug("IS BATTERY CHARGING: $it") } + + val batteryPercentage = batteryStatusIntent.let { intent -> + val level = intent.getIntExtra(BatteryManager.EXTRA_LEVEL, -1).takeUnless { it == -1 } ?: return@let -1F + val scale = intent.getIntExtra(BatteryManager.EXTRA_SCALE, -1).takeUnless { it == -1 } ?: return@let -1F + level * 100 / scale.toFloat() + }.also { logDebug("BATTERY PERCENTAGE: $it%") } + + return when { + batteryPercentage >= 80 || (isCharging && batteryPercentage >= 70) -> PerformanceLevel.EXCELLENT + batteryPercentage >= 55 || (isCharging && batteryPercentage >= 50) -> PerformanceLevel.HIGH + batteryPercentage >= 40 || (isCharging && batteryPercentage >= 35) -> PerformanceLevel.AVERAGE + else -> PerformanceLevel.LOW + } + } + + companion object: PerformanceManagerProvider { + + override fun create(applicationContext: Context, isInDebugMode: Boolean): PerformanceManager = + BatteryPerformanceManager(applicationContext, isInDebugMode) + + private const val DELAY_IN_SECS = 60F + } +} diff --git a/droid-dex/src/main/kotlin/com/blinkit/droiddex/constants/PerformanceClass.kt b/droid-dex/src/main/kotlin/com/blinkit/droiddex/constants/PerformanceClass.kt new file mode 100644 index 0000000..18668e3 --- /dev/null +++ b/droid-dex/src/main/kotlin/com/blinkit/droiddex/constants/PerformanceClass.kt @@ -0,0 +1,43 @@ +package com.blinkit.droiddex.constants + +import androidx.annotation.IntDef +import androidx.annotation.Keep + +@Keep +@IntDef( + PerformanceClass.CPU, + PerformanceClass.MEMORY, + PerformanceClass.STORAGE, + PerformanceClass.NETWORK, + PerformanceClass.BATTERY +) +@Retention(AnnotationRetention.SOURCE) +@Target( + AnnotationTarget.PROPERTY, + AnnotationTarget.VALUE_PARAMETER, + AnnotationTarget.CLASS, + AnnotationTarget.TYPE, + AnnotationTarget.FUNCTION +) +annotation class PerformanceClass { + + companion object { + + const val CPU = 0 + const val MEMORY = 1 + const val STORAGE = 2 + const val NETWORK = 3 + const val BATTERY = 4 + + internal fun values(): List = listOf(CPU, MEMORY, STORAGE, NETWORK, BATTERY) + + fun @PerformanceClass Int.name() = when (this) { + CPU -> "CPU" + MEMORY -> "MEMORY" + STORAGE -> "STORAGE" + NETWORK -> "NETWORK" + BATTERY -> "BATTERY" + else -> this.toString() + } + } +} diff --git a/droid-dex/src/main/kotlin/com/blinkit/droiddex/constants/PerformanceLevel.kt b/droid-dex/src/main/kotlin/com/blinkit/droiddex/constants/PerformanceLevel.kt new file mode 100644 index 0000000..618f432 --- /dev/null +++ b/droid-dex/src/main/kotlin/com/blinkit/droiddex/constants/PerformanceLevel.kt @@ -0,0 +1,15 @@ +package com.blinkit.droiddex.constants + +import androidx.annotation.Keep + +@Keep +enum class PerformanceLevel(val level: Int) { + + UNKNOWN(0), LOW(1), AVERAGE(2), HIGH(3), EXCELLENT(4); + + companion object { + + internal fun getPerformanceLevel(level: Int? = null): PerformanceLevel = + PerformanceLevel.values().find { it.level == level } ?: UNKNOWN + } +} diff --git a/droid-dex/src/main/kotlin/com/blinkit/droiddex/cpu/CpuPerformanceManager.kt b/droid-dex/src/main/kotlin/com/blinkit/droiddex/cpu/CpuPerformanceManager.kt new file mode 100644 index 0000000..b05fefb --- /dev/null +++ b/droid-dex/src/main/kotlin/com/blinkit/droiddex/cpu/CpuPerformanceManager.kt @@ -0,0 +1,93 @@ +package com.blinkit.droiddex.cpu + +import android.content.Context +import android.os.Build +import androidx.annotation.RequiresApi +import androidx.core.performance.DevicePerformance +import com.blinkit.droiddex.constants.PerformanceClass +import com.blinkit.droiddex.constants.PerformanceLevel +import com.blinkit.droiddex.cpu.utils.CpuInfoManager +import com.blinkit.droiddex.factory.base.PerformanceManager +import com.blinkit.droiddex.factory.providers.PerformanceManagerProvider +import com.blinkit.droiddex.utils.getApproxHeapLimitInMB +import com.blinkit.droiddex.utils.getTotalRamInGB +import java.util.Locale + +/** + * Inspired By Telegram's CPU Division + */ +internal class CpuPerformanceManager( + private val applicationContext: Context, isInDebugMode: Boolean +): PerformanceManager(isInDebugMode) { + + private val cpuInfoManager by lazy { CpuInfoManager(logger) } + + private val lowSocModels = intArrayOf( + -1775228513, // EXYNOS 850 + 802464304, // EXYNOS 7872 + 802464333, // EXYNOS 7880 + 802464302, // EXYNOS 7870 + 2067362118, // MSM8953 + 2067362060, // MSM8937 + 2067362084, // MSM8940 + 2067362241, // MSM8992 + 2067362117, // MSM8952 + 2067361998, // MSM8917 + -1853602818 // SDM439 + ) + + private val excellentMediaPerformanceClasses = + intArrayOf(Build.VERSION_CODES.S, Build.VERSION_CODES.S_V2, Build.VERSION_CODES.TIRAMISU) + + override fun getPerformanceClass() = PerformanceClass.CPU + + override fun getDelayInSecs() = DELAY_IN_SECS + + override fun measurePerformanceLevel(): PerformanceLevel { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) { + val socModel = getBuildSocModel() + if (lowSocModels.any { it == socModel }) { + logInfo("SOC MODEL: $socModel IN LIST OF LOW SOC MODELS") + return PerformanceLevel.LOW + } + } + + val coresCount = cpuInfoManager.noOfCores.also { logger.logDebug("CORE COUNT: $it") } + + val maxCpuFreq = cpuInfoManager.maxCpuFreqInMHz + + val ramInGB = getTotalRamInGB(applicationContext, logger) + + val androidVersion = getAndroidVersion() + + val mediaPerformanceClass = getMediaPerformanceClass() + + val approxHeapLimitInMB = getApproxHeapLimitInMB(applicationContext, logger) + + return if (mediaPerformanceClass in excellentMediaPerformanceClasses || ramInGB >= 12) { + PerformanceLevel.EXCELLENT + } else if (androidVersion < 21 || coresCount <= 2 || approxHeapLimitInMB <= 100 || coresCount <= 4 && maxCpuFreq <= 1250 || coresCount <= 4 && maxCpuFreq <= 1600 && approxHeapLimitInMB <= 128 && androidVersion <= 21 || coresCount <= 4 && maxCpuFreq <= 1300 && approxHeapLimitInMB <= 128 && androidVersion <= 24 || ramInGB <= 2) { + PerformanceLevel.LOW + } else if (coresCount < 8 || approxHeapLimitInMB <= 160 || maxCpuFreq <= 2055 || coresCount == 8 && androidVersion <= 23 || ramInGB <= 6) { + PerformanceLevel.AVERAGE + } else { + PerformanceLevel.HIGH + } + } + + @RequiresApi(Build.VERSION_CODES.S) + private fun getBuildSocModel() = Build.SOC_MODEL.uppercase(Locale.getDefault()).hashCode() + + private fun getMediaPerformanceClass() = + DevicePerformance.create(applicationContext).mediaPerformanceClass.also { logDebug("MEDIA PERFORMANCE CLASS: $it") } + + private fun getAndroidVersion() = Build.VERSION.SDK_INT.also { logDebug("ANDROID VERSION: $it") } + + companion object: PerformanceManagerProvider { + + override fun create(applicationContext: Context, isInDebugMode: Boolean): PerformanceManager = + CpuPerformanceManager(applicationContext, isInDebugMode) + + private const val DELAY_IN_SECS = 60F + } +} diff --git a/droid-dex/src/main/kotlin/com/blinkit/droiddex/cpu/utils/CpuInfoManager.kt b/droid-dex/src/main/kotlin/com/blinkit/droiddex/cpu/utils/CpuInfoManager.kt new file mode 100644 index 0000000..1fe041e --- /dev/null +++ b/droid-dex/src/main/kotlin/com/blinkit/droiddex/cpu/utils/CpuInfoManager.kt @@ -0,0 +1,86 @@ +package com.blinkit.droiddex.cpu.utils + +import com.blinkit.droiddex.utils.Logger +import com.blinkit.droiddex.utils.average +import java.io.BufferedReader +import java.io.File +import java.io.FileReader +import java.lang.Integer.max +import java.util.regex.Pattern + +internal class CpuInfoManager(private val logger: Logger) { + + private val coresFreqList = mutableListOf() + + val noOfCores: Int by lazy { + max( + runCatching { + val files = File(CPU_INFO_PATH).listFiles { pathname -> Pattern.matches("cpu[0-9]+", pathname.name) } + max(1, files?.size ?: 1) + }.getOrDefault(1), Runtime.getRuntime().availableProcessors() + ) + } + + val currentCpuUsage: Int + get() = (coresFreqList.map { it.currentUsagePercent }.average()?.toInt() + ?: 0).also { logger.logDebug("CURRENT USAGE: ${it}%") } + + val maxCpuFreqInMHz: Int + get() = ((coresFreqList.map { it.max }.average()?.toLong()?.takeIf { it > 0 }?.div(1000)?.toInt()) + ?: Int.MAX_VALUE).also { logger.logDebug("MAX CPU FREQUENCY: $it MHz") } + + val minCpuFreqInMHz: Int + get() = ((coresFreqList.map { it.min }.average()?.toLong()?.takeIf { it > 0 }?.div(1000)?.toInt()) + ?: Int.MAX_VALUE).also { logger.logDebug("MIN CPU FREQUENCY: $it MHz") } + + init { + for (i in 0 until noOfCores) coresFreqList.add(CoreFreq(i)) + } + + private class CoreFreq(private val index: Int) { + + var min = 0L + get() = field.takeIf { it > 0L } ?: getMinFreq().also { field = it } + private set + + var max = 0L + get() = field.takeIf { it > 0L } ?: getMaxFreq().also { field = it } + private set + + private var cur = 0L + + init { + min = getMinFreq() + max = getMaxFreq() + } + + val currentUsagePercent: Int + get() { + cur = getCurFreq() + + var percent = 0 + if (max - min > 0 && max > 0 && cur > 0) { + percent = ((cur - min) * 100 / (max - min)).toInt() + } + + return max(percent, 0) + } + + private fun getCurFreq() = readFile("${CPU_INFO_PATH}cpu$index/cpufreq/scaling_cur_freq") + + private fun getMinFreq() = readFile("${CPU_INFO_PATH}cpu$index/cpufreq/cpuinfo_min_freq") + + private fun getMaxFreq() = readFile("${CPU_INFO_PATH}cpu$index/cpufreq/cpuinfo_max_freq") + + private fun readFile(path: String): Long = try { + BufferedReader(FileReader(path)).use { reader -> reader.readLine()?.toLongOrNull() } + } catch (e: Exception) { + null + } ?: 0 + } + + companion object { + + private const val CPU_INFO_PATH = "/sys/devices/system/cpu/" + } +} diff --git a/droid-dex/src/main/kotlin/com/blinkit/droiddex/factory/base/PerformanceManager.kt b/droid-dex/src/main/kotlin/com/blinkit/droiddex/factory/base/PerformanceManager.kt new file mode 100644 index 0000000..8f9c5a7 --- /dev/null +++ b/droid-dex/src/main/kotlin/com/blinkit/droiddex/factory/base/PerformanceManager.kt @@ -0,0 +1,44 @@ +package com.blinkit.droiddex.factory.base + +import androidx.lifecycle.LiveData +import androidx.lifecycle.MutableLiveData +import com.blinkit.droiddex.constants.PerformanceClass +import com.blinkit.droiddex.constants.PerformanceLevel +import com.blinkit.droiddex.utils.Logger +import com.blinkit.droiddex.utils.runAsyncPeriodically + +internal abstract class PerformanceManager(private val isInDebugMode: Boolean) { + + private val _performanceLevelLd = MutableLiveData(PerformanceLevel.UNKNOWN) + val performanceLevelLd: LiveData + get() = _performanceLevelLd + + protected val logger by lazy { Logger(isInDebugMode, getPerformanceClass()) } + + fun init() { + runAsyncPeriodically({ + try { + measurePerformanceLevel().also { + val hasPerformanceLevelChanged = performanceLevelLd.value != it + if (hasPerformanceLevelChanged) _performanceLevelLd.postValue(it) + logger.logPerformanceLevelChange(it, hasPerformanceLevelChanged) + } + } catch (e: Exception) { + logger.logError(e) + } + }, delayInSecs = getDelayInSecs()) + } + + @PerformanceClass + protected abstract fun getPerformanceClass(): Int + + protected abstract fun getDelayInSecs(): Float + + protected abstract fun measurePerformanceLevel(): PerformanceLevel + + protected fun logInfo(message: String) = logger.logInfo(message) + + protected fun logDebug(message: String) = logger.logDebug(message) + + protected fun logError(throwable: Throwable) = logger.logError(throwable) +} diff --git a/droid-dex/src/main/kotlin/com/blinkit/droiddex/factory/factory/PerformanceManagerFactory.kt b/droid-dex/src/main/kotlin/com/blinkit/droiddex/factory/factory/PerformanceManagerFactory.kt new file mode 100644 index 0000000..4faf69a --- /dev/null +++ b/droid-dex/src/main/kotlin/com/blinkit/droiddex/factory/factory/PerformanceManagerFactory.kt @@ -0,0 +1,39 @@ +package com.blinkit.droiddex.factory.factory + +import android.content.Context +import androidx.lifecycle.LiveData +import com.blinkit.droiddex.battery.BatteryPerformanceManager +import com.blinkit.droiddex.constants.PerformanceClass +import com.blinkit.droiddex.constants.PerformanceLevel +import com.blinkit.droiddex.cpu.CpuPerformanceManager +import com.blinkit.droiddex.factory.base.PerformanceManager +import com.blinkit.droiddex.memory.MemoryPerformanceManager +import com.blinkit.droiddex.network.NetworkPerformanceManager +import com.blinkit.droiddex.storage.StoragePerformanceManager + +internal class PerformanceManagerFactory(private val applicationContext: Context, private val isInDebugMode: Boolean) { + + private val performanceManagerMap = mutableMapOf<@PerformanceClass Int, PerformanceManager>() + + init { + PerformanceClass.values().forEach { getOrPut(it) } + } + + fun getPerformanceLevel(@PerformanceClass performanceClass: Int): PerformanceLevel = + getOrPut(performanceClass).performanceLevelLd.value ?: PerformanceLevel.UNKNOWN + + fun getPerformanceLevelLd(@PerformanceClass performanceClass: Int): LiveData = + getOrPut(performanceClass).performanceLevelLd + + private fun getOrPut(@PerformanceClass performanceClass: Int): PerformanceManager = + performanceManagerMap.getOrPut(performanceClass) { + when (performanceClass) { + PerformanceClass.CPU -> CpuPerformanceManager.create(applicationContext, isInDebugMode) + PerformanceClass.MEMORY -> MemoryPerformanceManager.create(applicationContext, isInDebugMode) + PerformanceClass.STORAGE -> StoragePerformanceManager.create(applicationContext, isInDebugMode) + PerformanceClass.NETWORK -> NetworkPerformanceManager.create(applicationContext, isInDebugMode) + PerformanceClass.BATTERY -> BatteryPerformanceManager.create(applicationContext, isInDebugMode) + else -> throw IllegalArgumentException("NO SUCH PERFORMANCE CLASS EXISTS: $performanceClass") + }.apply { init() } + } +} diff --git a/droid-dex/src/main/kotlin/com/blinkit/droiddex/factory/providers/PerformanceManagerProvider.kt b/droid-dex/src/main/kotlin/com/blinkit/droiddex/factory/providers/PerformanceManagerProvider.kt new file mode 100644 index 0000000..127cb54 --- /dev/null +++ b/droid-dex/src/main/kotlin/com/blinkit/droiddex/factory/providers/PerformanceManagerProvider.kt @@ -0,0 +1,9 @@ +package com.blinkit.droiddex.factory.providers + +import android.content.Context +import com.blinkit.droiddex.factory.base.PerformanceManager + +internal interface PerformanceManagerProvider { + + fun create(applicationContext: Context, isInDebugMode: Boolean): PerformanceManager +} diff --git a/droid-dex/src/main/kotlin/com/blinkit/droiddex/memory/MemoryPerformanceManager.kt b/droid-dex/src/main/kotlin/com/blinkit/droiddex/memory/MemoryPerformanceManager.kt new file mode 100644 index 0000000..61eebb6 --- /dev/null +++ b/droid-dex/src/main/kotlin/com/blinkit/droiddex/memory/MemoryPerformanceManager.kt @@ -0,0 +1,51 @@ +package com.blinkit.droiddex.memory + +import android.content.Context +import com.blinkit.droiddex.constants.PerformanceClass +import com.blinkit.droiddex.constants.PerformanceLevel +import com.blinkit.droiddex.factory.base.PerformanceManager +import com.blinkit.droiddex.factory.providers.PerformanceManagerProvider +import com.blinkit.droiddex.utils.getApproxHeapLimitInMB +import com.blinkit.droiddex.utils.getApproxHeapRemainingInMB +import com.blinkit.droiddex.utils.getAvailableRamInGB +import com.blinkit.droiddex.utils.getMemoryInfo + +internal class MemoryPerformanceManager( + private val applicationContext: Context, isInDebugMode: Boolean +): PerformanceManager(isInDebugMode) { + + override fun getPerformanceClass() = PerformanceClass.MEMORY + + override fun getDelayInSecs() = DELAY_IN_SECS + + override fun measurePerformanceLevel(): PerformanceLevel { + if (getMemoryInfo(applicationContext, logger).lowMemory) { + logInfo("DEVICE HAS LOW MEMORY") + return PerformanceLevel.LOW + } + + val availableRamInGB = getAvailableRamInGB(applicationContext, logger) + + val approxHeapLimitInMB = getApproxHeapLimitInMB(applicationContext, logger) + + val approxHeapRemainingInMB = getApproxHeapRemainingInMB(applicationContext, logger) + + return if (approxHeapRemainingInMB <= 64 || approxHeapLimitInMB < 128) { + PerformanceLevel.LOW + } else if (approxHeapRemainingInMB <= 128 || approxHeapLimitInMB < 256 || availableRamInGB <= 2) { + PerformanceLevel.AVERAGE + } else if (approxHeapRemainingInMB <= 256 || availableRamInGB <= 3) { + PerformanceLevel.HIGH + } else { + PerformanceLevel.EXCELLENT + } + } + + companion object: PerformanceManagerProvider { + + override fun create(applicationContext: Context, isInDebugMode: Boolean): PerformanceManager = + MemoryPerformanceManager(applicationContext, isInDebugMode) + + private const val DELAY_IN_SECS = 10F + } +} diff --git a/droid-dex/src/main/kotlin/com/blinkit/droiddex/network/NetworkPerformanceManager.kt b/droid-dex/src/main/kotlin/com/blinkit/droiddex/network/NetworkPerformanceManager.kt new file mode 100644 index 0000000..5bfaf7e --- /dev/null +++ b/droid-dex/src/main/kotlin/com/blinkit/droiddex/network/NetworkPerformanceManager.kt @@ -0,0 +1,205 @@ +package com.blinkit.droiddex.network + +import android.content.Context +import android.net.ConnectivityManager +import android.net.NetworkCapabilities +import android.net.wifi.WifiManager +import android.os.Build +import android.telephony.CellInfoCdma +import android.telephony.CellInfoGsm +import android.telephony.CellInfoLte +import android.telephony.CellInfoWcdma +import android.telephony.TelephonyManager +import androidx.annotation.IntRange +import androidx.annotation.RequiresApi +import com.blinkit.droiddex.constants.PerformanceClass +import com.blinkit.droiddex.constants.PerformanceLevel +import com.blinkit.droiddex.factory.base.PerformanceManager +import com.blinkit.droiddex.factory.providers.PerformanceManagerProvider +import com.blinkit.droiddex.network.utils.BandwidthManager +import com.blinkit.droiddex.utils.getPerformanceLevelWithWeights + +internal class NetworkPerformanceManager( + private val applicationContext: Context, isInDebugMode: Boolean +): PerformanceManager(isInDebugMode) { + + private val bandwidthManager by lazy { BandwidthManager(logger) } + + override fun getPerformanceClass() = PerformanceClass.NETWORK + + override fun getDelayInSecs() = DELAY_IN_SECS + + override fun measurePerformanceLevel(): PerformanceLevel { + val bandwidthAverage = bandwidthManager.addSampleAndRecalculateBandwidthAverage() + + if (isInternetConnected().not()) { + logInfo("DEVICE HAS NO INTERNET") + return PerformanceLevel.LOW + } + + val bandwidthAverageStrengthLevel = getBandwidthAverageStrengthLevel(bandwidthAverage) + val downloadSpeedLevel = getDownloadSpeedStrengthLevel() + val signalStrengthLevel = getSignalStrengthLevel() + + return getPerformanceLevelWithWeights(mutableListOf>().apply { + bandwidthAverageStrengthLevel?.let { add(Pair(it, 2F)) } + downloadSpeedLevel?.let { add(Pair(it, 1F)) } + signalStrengthLevel?.let { add(Pair(it, 1F)) } + }) + } + + private fun isInternetConnected(): Boolean { + val connectivityManager = getConnectivityManager() ?: return false + return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { + val capabilities = connectivityManager.getNetworkCapabilities(connectivityManager.activeNetwork) + capabilities?.let { + it.hasTransport(NetworkCapabilities.TRANSPORT_WIFI) || it.hasTransport(NetworkCapabilities.TRANSPORT_CELLULAR) + } + } else { + @Suppress("DEPRECATION") connectivityManager.activeNetworkInfo?.isConnected + } ?: false + } + + private fun getBandwidthAverageStrengthLevel(bandwidthAverage: Double) = when { + bandwidthAverage <= 0 -> null + bandwidthAverage < AVERAGE_BANDWIDTH_THRESHOLD -> PerformanceLevel.LOW + bandwidthAverage < HIGH_BANDWIDTH_THRESHOLD -> PerformanceLevel.AVERAGE + bandwidthAverage < EXCELLENT_BANDWIDTH_THRESHOLD -> PerformanceLevel.HIGH + else -> PerformanceLevel.EXCELLENT + }?.also { logDebug("BANDWIDTH AVERAGE STRENGTH TYPE: ${it.name}") } + + @RequiresApi(Build.VERSION_CODES.M) + private fun getDownloadSpeed(): Int { + val connectivityManager = getConnectivityManager() ?: return 0 + val network = connectivityManager.activeNetwork ?: return 0 + val networkCapabilities = connectivityManager.getNetworkCapabilities(network) + return (networkCapabilities?.linkDownstreamBandwidthKbps ?: 0).also { logDebug("DOWNLOAD SPEED: $it Kb/s") } + } + + private fun getDownloadSpeedStrengthLevel() = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { + val downloadSpeed = getDownloadSpeed() + when { + downloadSpeed >= EXCELLENT_DOWNLOAD_SPEED_THRESHOLD -> PerformanceLevel.EXCELLENT + downloadSpeed >= HIGH_DOWNLOAD_SPEED_THRESHOLD -> PerformanceLevel.HIGH + downloadSpeed >= AVERAGE_DOWNLOAD_SPEED_THRESHOLD -> PerformanceLevel.AVERAGE + else -> PerformanceLevel.LOW + }.also { logDebug("DOWNLOAD SPEED TYPE: ${it.name}") } + } else null + + private fun getNetworkType(): NetworkType { + val connectivityManager = getConnectivityManager() ?: return NetworkType.UNKNOWN + return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { + val activeNetwork = connectivityManager.getNetworkCapabilities(connectivityManager.activeNetwork) + ?: return NetworkType.UNKNOWN + when { + activeNetwork.hasTransport(NetworkCapabilities.TRANSPORT_WIFI) -> NetworkType.WIFI + activeNetwork.hasTransport(NetworkCapabilities.TRANSPORT_CELLULAR) -> NetworkType.CELLULAR + else -> NetworkType.UNKNOWN + } + } else { + @Suppress("DEPRECATION") when (connectivityManager.activeNetworkInfo?.type) { + ConnectivityManager.TYPE_WIFI -> NetworkType.WIFI + ConnectivityManager.TYPE_MOBILE -> NetworkType.CELLULAR + else -> NetworkType.UNKNOWN + } + }.also { logDebug("NETWORK TYPE: ${it.name}") } + } + + @IntRange(from = 0, to = 4) + private fun getWifiSignalLevel(): Int { + val wifiManger = + applicationContext.applicationContext.getSystemService(Context.WIFI_SERVICE) as? WifiManager ?: return 0 + @Suppress("DEPRECATION") val wifiInfo = wifiManger.connectionInfo + return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) { + wifiManger.calculateSignalLevel(wifiInfo.rssi) + } else { + @Suppress("DEPRECATION") WifiManager.calculateSignalLevel(wifiInfo.rssi, 5) + }.also { logDebug("WIFI SIGNAL LEVEL: $it") } + } + + @IntRange(from = 0, to = 4) + private fun getCellularInternetStrength(): Int { + val telephonyManager = + applicationContext.getSystemService(Context.TELEPHONY_SERVICE) as? TelephonyManager ?: return 0 + + val signalLevel = (try { + when (val info = telephonyManager.allCellInfo?.firstOrNull()) { + is CellInfoLte -> info.cellSignalStrength.level + is CellInfoGsm -> info.cellSignalStrength.level + is CellInfoCdma -> info.cellSignalStrength.level + is CellInfoWcdma -> info.cellSignalStrength.level + else -> 0 + } + } catch (e: SecurityException) { + 0 + }).also { logDebug("CELLULAR SIGNAL LEVEL: $it") } + + val networkGeneration = (try { + val networkType = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { + telephonyManager.dataNetworkType + } else { + @Suppress("DEPRECATION") telephonyManager.networkType + } + @Suppress("DEPRECATION") when (networkType) { + TelephonyManager.NETWORK_TYPE_GPRS, TelephonyManager.NETWORK_TYPE_EDGE, TelephonyManager.NETWORK_TYPE_CDMA, TelephonyManager.NETWORK_TYPE_1xRTT, TelephonyManager.NETWORK_TYPE_IDEN -> NetworkGeneration.NETWORK_2G + TelephonyManager.NETWORK_TYPE_UMTS, TelephonyManager.NETWORK_TYPE_EVDO_0, TelephonyManager.NETWORK_TYPE_EVDO_A, TelephonyManager.NETWORK_TYPE_HSDPA, TelephonyManager.NETWORK_TYPE_HSUPA, TelephonyManager.NETWORK_TYPE_HSPA, TelephonyManager.NETWORK_TYPE_EVDO_B, TelephonyManager.NETWORK_TYPE_EHRPD, TelephonyManager.NETWORK_TYPE_HSPAP -> NetworkGeneration.NETWORK_3G + TelephonyManager.NETWORK_TYPE_LTE -> NetworkGeneration.NETWORK_4G + TelephonyManager.NETWORK_TYPE_NR -> NetworkGeneration.NETWORK_5G + else -> NetworkGeneration.UNKNOWN + } + } catch (e: SecurityException) { + NetworkGeneration.UNKNOWN + }).also { logDebug("CELLULAR NETWORK GENERATION: ${it.name}") } + + return when (networkGeneration) { + NetworkGeneration.UNKNOWN -> 0 + NetworkGeneration.NETWORK_2G -> (1 + signalLevel) / 2 + NetworkGeneration.NETWORK_3G -> (2 + signalLevel) / 2 + NetworkGeneration.NETWORK_4G -> (3 + signalLevel) / 2 + NetworkGeneration.NETWORK_5G -> (4 + signalLevel) / 2 + }.also { logDebug("CELLULAR SIGNAL STRENGTH: $it") } + } + + private fun categorizeSignalThreshold(signal: Int): PerformanceLevel = when { + signal >= EXCELLENT_SIGNAL_THRESHOLD -> PerformanceLevel.EXCELLENT + signal >= HIGH_SIGNAL_THRESHOLD -> PerformanceLevel.HIGH + signal >= AVERAGE_SIGNAL_THRESHOLD -> PerformanceLevel.AVERAGE + else -> PerformanceLevel.LOW + } + + private fun getSignalStrengthLevel() = when (getNetworkType()) { + NetworkType.WIFI -> categorizeSignalThreshold(getWifiSignalLevel()) + NetworkType.CELLULAR -> categorizeSignalThreshold(getCellularInternetStrength()) + else -> null + }?.also { logDebug("SIGNAL STRENGTH TYPE: ${it.name}") } + + private fun getConnectivityManager(): ConnectivityManager? = + applicationContext.getSystemService(Context.CONNECTIVITY_SERVICE) as? ConnectivityManager ?: run { + logger.logError(Throwable("CONNECTIVITY MANAGER IS NULL")) + null + } + + private enum class NetworkType { UNKNOWN, CELLULAR, WIFI } + + private enum class NetworkGeneration { UNKNOWN, NETWORK_2G, NETWORK_3G, NETWORK_4G, NETWORK_5G } + + companion object: PerformanceManagerProvider { + + override fun create(applicationContext: Context, isInDebugMode: Boolean): PerformanceManager = + NetworkPerformanceManager(applicationContext, isInDebugMode) + + private const val DELAY_IN_SECS = 2.5F + + private const val EXCELLENT_BANDWIDTH_THRESHOLD = 2000F // 2 Mbps + private const val HIGH_BANDWIDTH_THRESHOLD = 550F // 0.55 Mbps + private const val AVERAGE_BANDWIDTH_THRESHOLD = 150F // 0.15 Mbps + + private const val EXCELLENT_DOWNLOAD_SPEED_THRESHOLD = 10000 // 10 Mbps + private const val HIGH_DOWNLOAD_SPEED_THRESHOLD = 5000 // 5 Mbps + private const val AVERAGE_DOWNLOAD_SPEED_THRESHOLD = 2000 // 2 Mbps + + private const val EXCELLENT_SIGNAL_THRESHOLD = 4 + private const val HIGH_SIGNAL_THRESHOLD = 3 + private const val AVERAGE_SIGNAL_THRESHOLD = 2 + } +} diff --git a/droid-dex/src/main/kotlin/com/blinkit/droiddex/network/utils/BandwidthManager.kt b/droid-dex/src/main/kotlin/com/blinkit/droiddex/network/utils/BandwidthManager.kt new file mode 100644 index 0000000..f11df56 --- /dev/null +++ b/droid-dex/src/main/kotlin/com/blinkit/droiddex/network/utils/BandwidthManager.kt @@ -0,0 +1,91 @@ +package com.blinkit.droiddex.network.utils + +import android.net.TrafficStats +import android.os.SystemClock +import com.blinkit.droiddex.utils.Logger +import kotlin.math.ceil +import kotlin.math.exp +import kotlin.math.ln + +/** + * Inspired By Facebook's Network Connection Class + */ +internal class BandwidthManager(private val logger: Logger) { + + private var lastReadTime: Long = SystemClock.elapsedRealtime() + + private var lastDownloadBytes: Long = TrafficStats.getTotalRxBytes() + + private val downloadBandwidth = ExponentialGeometricAverage(DECAY_CONSTANT) + + fun addSampleAndRecalculateBandwidthAverage(): Double { + val currentBytes = TrafficStats.getTotalRxBytes() + val currentTime = SystemClock.elapsedRealtime() + + addBandwidth(currentBytes - lastDownloadBytes, currentTime - lastReadTime) + + lastReadTime = currentTime + lastDownloadBytes = currentBytes + + return downloadBandwidth.average.also { logger.logDebug("BANDWIDTH AVERAGE: $it Kb/s") } + } + + private fun addBandwidth(bytes: Long, timeInMs: Long) { + val bandwidth = ((bytes * 1.0) / timeInMs) * BYTES_TO_BITS + if (timeInMs == 0L || bandwidth < BANDWIDTH_LOWER_BOUND) { + return + } + + downloadBandwidth.addValue(bandwidth) + } + + private class ExponentialGeometricAverage(private val decayConstant: Double) { + + var average = -1.0 + private set + + private val cutOver: Int = if (decayConstant == 0.0) Int.MAX_VALUE else ceil(1 / decayConstant).toInt() + + private var count = 0 + + // Adds a new value to the moving average + fun addValue(value: Double) { + val keepConstant = 1 - decayConstant + average = if (count > cutOver) { + exp(keepConstant * ln(average) + decayConstant * ln(value)) + } else if (count > 0) { + val retained = keepConstant * count / (count + 1.0) + val newcomer = 1.0 - retained + exp(retained * ln(average) + newcomer * ln(value)) + } else { + value + } + count++ + } + + // Resets the moving average + fun reset() { + average = -1.0 + count = 0 + } + } + + companion object { + + private const val BYTES_TO_BITS = 8 + + /** + * The factor used to calculate the current bandwidth + * depending upon the previous calculated value for bandwidth. + * + * The smaller this value is, the less responsive to new samples the moving average becomes. + */ + private const val DECAY_CONSTANT = 0.05 + + /** + * The lower bound for measured bandwidth in bits/ms. Readings + * lower than this are treated as effectively zero (therefore ignored). + */ + private const val BANDWIDTH_LOWER_BOUND = 10 + } +} diff --git a/droid-dex/src/main/kotlin/com/blinkit/droiddex/storage/StoragePerformanceManager.kt b/droid-dex/src/main/kotlin/com/blinkit/droiddex/storage/StoragePerformanceManager.kt new file mode 100644 index 0000000..446aa16 --- /dev/null +++ b/droid-dex/src/main/kotlin/com/blinkit/droiddex/storage/StoragePerformanceManager.kt @@ -0,0 +1,47 @@ +package com.blinkit.droiddex.storage + +import android.annotation.SuppressLint +import android.content.Context +import android.os.Environment +import com.blinkit.droiddex.constants.PerformanceClass +import com.blinkit.droiddex.constants.PerformanceLevel +import com.blinkit.droiddex.factory.base.PerformanceManager +import com.blinkit.droiddex.factory.providers.PerformanceManagerProvider +import com.blinkit.droiddex.utils.convertBytesToGB + +internal class StoragePerformanceManager(isInDebugMode: Boolean): PerformanceManager(isInDebugMode) { + + override fun getPerformanceClass() = PerformanceClass.STORAGE + + override fun getDelayInSecs() = DELAY_IN_SECS + + override fun measurePerformanceLevel() = with(getStorageLeftInGB()) { + when { + this > 16 -> PerformanceLevel.EXCELLENT + this > 8 -> PerformanceLevel.HIGH + this > 4 -> PerformanceLevel.AVERAGE + this > 0 -> PerformanceLevel.LOW + else -> PerformanceLevel.UNKNOWN + } + } + + @SuppressLint("UsableSpace") + private fun getStorageLeftInGB(): Float { + val totalStorageLeft = try { + convertBytesToGB(Environment.getExternalStorageDirectory().usableSpace) + } catch (e: SecurityException) { + logError(e) + 0F + } + + return totalStorageLeft.also { logDebug("TOTAL STORAGE LEFT: $it GB") } + } + + companion object: PerformanceManagerProvider { + + override fun create(applicationContext: Context, isInDebugMode: Boolean): PerformanceManager = + StoragePerformanceManager(isInDebugMode) + + private const val DELAY_IN_SECS = 600F + } +} diff --git a/droid-dex/src/main/kotlin/com/blinkit/droiddex/utils/Logger.kt b/droid-dex/src/main/kotlin/com/blinkit/droiddex/utils/Logger.kt new file mode 100644 index 0000000..812896f --- /dev/null +++ b/droid-dex/src/main/kotlin/com/blinkit/droiddex/utils/Logger.kt @@ -0,0 +1,38 @@ +package com.blinkit.droiddex.utils + +import com.blinkit.droiddex.constants.PerformanceClass +import com.blinkit.droiddex.constants.PerformanceClass.Companion.name +import com.blinkit.droiddex.constants.PerformanceLevel +import timber.log.Timber + +internal class Logger(var isInDebugMode: Boolean = false, @PerformanceClass private val performanceClass: Int? = null) { + + fun logPerformanceLevelChange(level: PerformanceLevel, hasPerformanceLevelChanged: Boolean) { + if (hasPerformanceLevelChanged || isInDebugMode) logInfo("PERFORMANCE LEVEL: ${level.name}") + } + + fun logPerformanceLevelResult( + vararg classes: Pair<@PerformanceClass Int, Float>, performanceLevel: PerformanceLevel + ) { + logDebug("PERFORMANCE CLASSES: ${ + classes.joinToString(", ") { it.first.name() + if (it.second != 1F) "(WEIGHT: ${it.second})" else "" } + } | PERFORMANCE LEVEL: $performanceLevel") + } + + fun logInfo(message: String) { + getTimber().i(beautifyMessage(message)) + } + + fun logDebug(message: String) { + getTimber().d(beautifyMessage(message)) + } + + fun logError(exception: Throwable) { + getTimber().e(exception) + } + + private fun getTimber() = Timber.tag("DROID-DEX") + + private fun beautifyMessage(message: String): String = + performanceClass?.let { "${it.name()} | $message" } ?: message +} diff --git a/droid-dex/src/main/kotlin/com/blinkit/droiddex/utils/MemoryUtils.kt b/droid-dex/src/main/kotlin/com/blinkit/droiddex/utils/MemoryUtils.kt new file mode 100644 index 0000000..92b5a35 --- /dev/null +++ b/droid-dex/src/main/kotlin/com/blinkit/droiddex/utils/MemoryUtils.kt @@ -0,0 +1,49 @@ +package com.blinkit.droiddex.utils + +import android.app.ActivityManager +import android.content.Context +import kotlin.math.min + +internal fun getMemoryInfo(applicationContext: Context, logger: Logger): ActivityManager.MemoryInfo = + ActivityManager.MemoryInfo().also { getActivityManager(applicationContext, logger)?.getMemoryInfo(it) } + +/** + * Detailed Explanation + * + * Max Memory gives the actual limit for the heap + * Memory Class gives the limit we should be respecting + * + * In most cases for non-rooted devices, memory class is a better representation of the limit that should be made. + * But, it can be changed in rooted devices or devices with developer options switched on. Max Memory cannot be changed. + * So to get a better limit, we check minimum of memory class and max memory. + */ +internal fun getApproxHeapLimitInMB(applicationContext: Context, logger: Logger): Float { + val memoryClassInMB = getActivityManager(applicationContext, logger)?.memoryClass?.toFloat() ?: Float.MAX_VALUE + val maxMemoryInMB = convertBytesToMB(Runtime.getRuntime().maxMemory()) + + logger.logDebug("MEMORY CLASS: $memoryClassInMB MB, MAX MEMORY: $maxMemoryInMB MB") + + return min(memoryClassInMB, maxMemoryInMB).also { logger.logDebug("APPROXIMATE HEAP LIMIT: $it MB") } +} + +internal fun getApproxHeapRemainingInMB(applicationContext: Context, logger: Logger): Float { + val heapLimitInMB = getApproxHeapLimitInMB(applicationContext, logger) + val heapUsedInMB = convertBytesToMB(Runtime.getRuntime().totalMemory() - Runtime.getRuntime().freeMemory()) + + logger.logDebug("APPROXIMATE HEAP USED: $heapUsedInMB MB") + + return (heapLimitInMB - heapUsedInMB).also { logger.logDebug("APPROXIMATE HEAP REMAINING: $it MB") } +} + +internal fun getTotalRamInGB(applicationContext: Context, logger: Logger) = + convertBytesToGB(getMemoryInfo(applicationContext, logger).totalMem).also { logger.logDebug("TOTAL RAM: $it GB") } + +internal fun getAvailableRamInGB(applicationContext: Context, logger: Logger) = convertBytesToGB( + getMemoryInfo(applicationContext, logger).availMem +).also { logger.logDebug("AVAILABLE RAM: $it GB") } + +private fun getActivityManager(applicationContext: Context, logger: Logger): ActivityManager? = + applicationContext.getSystemService(Context.ACTIVITY_SERVICE) as? ActivityManager ?: run { + logger.logError(Throwable("ACTIVITY MANAGER IS NULL")) + null + } diff --git a/droid-dex/src/main/kotlin/com/blinkit/droiddex/utils/Utils.kt b/droid-dex/src/main/kotlin/com/blinkit/droiddex/utils/Utils.kt new file mode 100644 index 0000000..c15f822 --- /dev/null +++ b/droid-dex/src/main/kotlin/com/blinkit/droiddex/utils/Utils.kt @@ -0,0 +1,65 @@ +package com.blinkit.droiddex.utils + +import androidx.lifecycle.Lifecycle +import androidx.lifecycle.LiveData +import androidx.lifecycle.MediatorLiveData +import androidx.lifecycle.ProcessLifecycleOwner +import androidx.lifecycle.lifecycleScope +import androidx.lifecycle.repeatOnLifecycle +import com.blinkit.droiddex.constants.PerformanceLevel +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.delay +import kotlinx.coroutines.launch +import kotlinx.coroutines.withContext +import kotlin.math.ceil +import kotlin.math.roundToInt + +internal fun convertBytesToMB(value: Long): Float = (value * 1.0F) / (1024 * 1024) + +internal fun convertBytesToGB(value: Long): Float = convertBytesToMB(value) / 1024 + +internal fun floor(value: Float): Int = kotlin.math.floor(value).roundToInt() + +internal fun List.average() = if (isNotEmpty()) ceil(sumOf { it.toDouble() } / size) else null + +internal fun getPerformanceLevelWithWeights(performanceLevelWithWeights: List>): PerformanceLevel { + var weightedSum = 0F + var totalWeight = 0F + + performanceLevelWithWeights.filter { it.first != PerformanceLevel.UNKNOWN }.forEach { (level, weight) -> + weightedSum += level.level * weight + totalWeight += weight + } + + return PerformanceLevel.getPerformanceLevel(if (totalWeight != 0F) floor(weightedSum / totalWeight) else 0) +} + +internal fun getPerformanceLevelLdWithWeights( + performanceLevelLdWithWeights: List, Float>>, onChanged: (PerformanceLevel) -> Unit +): LiveData { + return MediatorLiveData().apply { + performanceLevelLdWithWeights.forEach { performanceLevelLdWithWeight -> + addSource(performanceLevelLdWithWeight.first) { + val performanceLevel = getPerformanceLevelWithWeights(performanceLevelLdWithWeights.mapNotNull { + it.first.value?.let { performanceLevel -> Pair(performanceLevel, it.second) } + }) + if (value != performanceLevel) { + value = performanceLevel + onChanged(performanceLevel) + } + } + } + } +} + +internal fun runAsyncPeriodically(block: () -> Unit, delayInSecs: Float) = with(ProcessLifecycleOwner.get()) { + block() + lifecycleScope.launch { + repeatOnLifecycle(Lifecycle.State.RESUMED) { + while (true) { + withContext(Dispatchers.IO) { block() } + delay((delayInSecs * 1000).toLong()) + } + } + } +} diff --git a/example/.gitignore b/example/.gitignore new file mode 100644 index 0000000..42afabf --- /dev/null +++ b/example/.gitignore @@ -0,0 +1 @@ +/build \ No newline at end of file diff --git a/example/build.gradle.kts b/example/build.gradle.kts new file mode 100644 index 0000000..e9a8c63 --- /dev/null +++ b/example/build.gradle.kts @@ -0,0 +1,49 @@ +@file:Suppress("UnstableApiUsage") + +plugins { + alias(libs.plugins.android.application) + alias(libs.plugins.jetbrains.kotlin.android) +} + + +android { + namespace = "com.blinkit.droiddexexample" + + buildFeatures { viewBinding = true } + + compileSdk = libs.versions.compileSdk.get().toInt() + + defaultConfig { + applicationId = "com.blinkit.droiddexexample" + + minSdk = 24 + targetSdk = libs.versions.targetSdk.get().toInt() + + versionCode = 1 + versionName = "1.0" + } + + buildTypes { + release { + isMinifyEnabled = false + proguardFiles(getDefaultProguardFile("proguard-android-optimize.txt"), "proguard-rules.pro") + } + } + + compileOptions { + sourceCompatibility = JavaVersion.toVersion(libs.versions.java.get()) + targetCompatibility = JavaVersion.toVersion(libs.versions.java.get()) + } + kotlinOptions { jvmTarget = libs.versions.java.get() } +} + + +dependencies { + implementation(project(":droid-dex")) + + implementation(libs.timber) + + implementation(libs.bundles.core) + + implementation(libs.bundles.ui) +} diff --git a/example/src/main/AndroidManifest.xml b/example/src/main/AndroidManifest.xml new file mode 100644 index 0000000..69fc45f --- /dev/null +++ b/example/src/main/AndroidManifest.xml @@ -0,0 +1,22 @@ + + + + + + + + + + + + + diff --git a/example/src/main/ic_launcher-playstore.png b/example/src/main/ic_launcher-playstore.png new file mode 100644 index 0000000..e8e00ff Binary files /dev/null and b/example/src/main/ic_launcher-playstore.png differ diff --git a/example/src/main/kotlin/com/blinkit/droiddexexample/ExampleApplication.kt b/example/src/main/kotlin/com/blinkit/droiddexexample/ExampleApplication.kt new file mode 100644 index 0000000..908fe6e --- /dev/null +++ b/example/src/main/kotlin/com/blinkit/droiddexexample/ExampleApplication.kt @@ -0,0 +1,16 @@ +package com.blinkit.droiddexexample + +import android.app.Application +import com.blinkit.droiddex.DroidDex +import timber.log.Timber + +class ExampleApplication: Application() { + + override fun onCreate() { + super.onCreate() + + Timber.plant(Timber.DebugTree()) + + DroidDex.init(this, BuildConfig.DEBUG) + } +} diff --git a/example/src/main/kotlin/com/blinkit/droiddexexample/main/MainActivity.kt b/example/src/main/kotlin/com/blinkit/droiddexexample/main/MainActivity.kt new file mode 100644 index 0000000..e158f1c --- /dev/null +++ b/example/src/main/kotlin/com/blinkit/droiddexexample/main/MainActivity.kt @@ -0,0 +1,69 @@ +package com.blinkit.droiddexexample.main + +import android.os.Bundle +import androidx.appcompat.app.AppCompatActivity +import com.blinkit.droiddex.DroidDex +import com.blinkit.droiddex.constants.PerformanceClass +import com.blinkit.droiddex.constants.PerformanceClass.Companion.name +import com.blinkit.droiddexexample.R +import com.blinkit.droiddexexample.databinding.ActivityMainBinding +import com.blinkit.droiddexexample.utils.dpToPx +import com.blinkit.droiddexexample.views.ItemView + +class MainActivity: AppCompatActivity() { + + private val binding: ActivityMainBinding by lazy { ActivityMainBinding.inflate(layoutInflater) } + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + setContentView(binding.root) + + binding.scrollView.setOnScrollChangeListener { _, _, scrollY, _, _ -> + binding.logo.elevation = (if (scrollY > 0.dpToPx()) 2 else 0).dpToPx() + } + + binding.headingIndividual.setTextAppearance(R.style.TextAppearanceBold) + binding.headingAggregate.setTextAppearance(R.style.TextAppearanceBold) + binding.headingWeightedAggregate.setTextAppearance(R.style.TextAppearanceBold) + + setupClasses(binding.cpu, PerformanceClass.CPU) + setupClasses(binding.memory, PerformanceClass.MEMORY) + setupClasses(binding.network, PerformanceClass.NETWORK) + setupClasses(binding.storage, PerformanceClass.STORAGE) + setupClasses(binding.battery, PerformanceClass.BATTERY) + + setupClasses(binding.cpuAndMemory, PerformanceClass.CPU, PerformanceClass.MEMORY) + setupClasses(binding.networkAndStorage, PerformanceClass.NETWORK, PerformanceClass.STORAGE) + setupClasses( + binding.cpuMemoryAndNetwork, PerformanceClass.CPU, PerformanceClass.MEMORY, PerformanceClass.NETWORK + ) + + setupWeightedClasses( + binding.memoryAndNetworkWeighted, PerformanceClass.MEMORY to 2F, PerformanceClass.NETWORK to 1F + ) + setupWeightedClasses( + binding.cpuAndBatteryWeighted, PerformanceClass.CPU to 2F, PerformanceClass.BATTERY to 3F + ) + } + + private fun setupClasses(item: ItemView, @PerformanceClass vararg performanceClasses: Int) { + DroidDex.getPerformanceLevelLd(*performanceClasses).observe(this) { level -> + item.set(level, performanceClasses.joinToString(" + ") { getClassName(it) }) + } + } + + private fun setupWeightedClasses( + item: ItemView, vararg performanceClassesWithWeights: Pair<@PerformanceClass Int, Float> + ) { + DroidDex.getWeightedPerformanceLevelLd(*performanceClassesWithWeights).observe(this) { level -> + item.set(level, performanceClassesWithWeights.joinToString(" +\n") { + getClassName(it.first) + " * " + it.second + }) + } + } + + private fun getClassName(@PerformanceClass performanceClass: Int) = when (performanceClass) { + PerformanceClass.CPU -> performanceClass.name() + else -> performanceClass.name().lowercase().replaceFirstChar { it.uppercase() } + } +} diff --git a/example/src/main/kotlin/com/blinkit/droiddexexample/utils/Extensions.kt b/example/src/main/kotlin/com/blinkit/droiddexexample/utils/Extensions.kt new file mode 100644 index 0000000..56a248d --- /dev/null +++ b/example/src/main/kotlin/com/blinkit/droiddexexample/utils/Extensions.kt @@ -0,0 +1,20 @@ +package com.blinkit.droiddexexample.utils + +import android.content.Context +import android.content.res.Resources +import androidx.annotation.ColorInt +import com.blinkit.droiddex.constants.PerformanceLevel +import com.blinkit.droiddexexample.R + +fun Int.dpToPx() = (this * Resources.getSystem().displayMetrics.density) + +@ColorInt +fun PerformanceLevel.getColor(context: Context) = context.getColor( + when (this) { + PerformanceLevel.EXCELLENT -> R.color.excellent + PerformanceLevel.HIGH -> R.color.high + PerformanceLevel.AVERAGE -> R.color.average + PerformanceLevel.LOW -> R.color.low + PerformanceLevel.UNKNOWN -> android.R.color.black + } +) diff --git a/example/src/main/kotlin/com/blinkit/droiddexexample/views/ItemView.kt b/example/src/main/kotlin/com/blinkit/droiddexexample/views/ItemView.kt new file mode 100644 index 0000000..394a757 --- /dev/null +++ b/example/src/main/kotlin/com/blinkit/droiddexexample/views/ItemView.kt @@ -0,0 +1,72 @@ +package com.blinkit.droiddexexample.views + +import android.animation.ObjectAnimator +import android.content.Context +import android.graphics.drawable.GradientDrawable +import android.util.AttributeSet +import android.view.LayoutInflater +import androidx.constraintlayout.widget.ConstraintLayout +import androidx.core.animation.doOnCancel +import androidx.core.animation.doOnEnd +import androidx.core.view.updatePadding +import com.blinkit.droiddex.constants.PerformanceLevel +import com.blinkit.droiddexexample.R +import com.blinkit.droiddexexample.databinding.LayoutItemBinding +import com.blinkit.droiddexexample.utils.dpToPx +import com.blinkit.droiddexexample.utils.getColor +import kotlin.math.abs + +class ItemView @JvmOverloads constructor( + context: Context, + attrs: AttributeSet? = null, + defStyleAttr: Int = 0, +): ConstraintLayout(context, attrs, defStyleAttr) { + + private val binding: LayoutItemBinding by lazy { LayoutItemBinding.inflate(LayoutInflater.from(context), this) } + + private var currentProgress = 0 + + private var animation: ObjectAnimator? = null + set(value) { + field?.cancel() + field = value + } + + init { + layoutParams = LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT).apply { + updatePadding(bottom = 8.dpToPx().toInt()) + } + + background = GradientDrawable().apply { + shape = GradientDrawable.RECTANGLE + setColor(context.getColor(android.R.color.white)) + cornerRadius = 12.dpToPx() + } + } + + fun set(level: PerformanceLevel, classText: String) { + binding.progressBar.progressDrawable = getProgressBackground(level) + + val newProgress = level.level * 25 + animation = ObjectAnimator.ofInt(binding.progressBar, "progress", currentProgress, newProgress).apply { + duration = abs(newProgress - currentProgress) * 5L + doOnCancel { currentProgress = newProgress } + doOnEnd { + currentProgress = newProgress + binding.progressBar.progress = newProgress + } + start() + } + + binding.level.text = level.name.lowercase().replaceFirstChar { it.uppercase() } + binding.level.setTextAppearance(R.style.TextAppearanceLight) + + binding.className.text = classText + binding.className.setTextAppearance(R.style.TextAppearanceMedium) + } + + private fun getProgressBackground(level: PerformanceLevel) = GradientDrawable().apply { + shape = GradientDrawable.RING + setColor(level.getColor(context)) + } +} diff --git a/example/src/main/res/drawable-v24/ic_launcher_background.xml b/example/src/main/res/drawable-v24/ic_launcher_background.xml new file mode 100644 index 0000000..56d85ac --- /dev/null +++ b/example/src/main/res/drawable-v24/ic_launcher_background.xml @@ -0,0 +1,23 @@ + + + + + + + + + + diff --git a/example/src/main/res/drawable/ic_launcher_foreground.xml b/example/src/main/res/drawable/ic_launcher_foreground.xml new file mode 100644 index 0000000..7557419 --- /dev/null +++ b/example/src/main/res/drawable/ic_launcher_foreground.xml @@ -0,0 +1,39 @@ + + + + + + + + + + + + + diff --git a/example/src/main/res/drawable/info_icon.xml b/example/src/main/res/drawable/info_icon.xml new file mode 100644 index 0000000..ba10678 --- /dev/null +++ b/example/src/main/res/drawable/info_icon.xml @@ -0,0 +1,17 @@ + + + + + + + + + + + diff --git a/example/src/main/res/drawable/logo.xml b/example/src/main/res/drawable/logo.xml new file mode 100644 index 0000000..4c57edc --- /dev/null +++ b/example/src/main/res/drawable/logo.xml @@ -0,0 +1,151 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/example/src/main/res/drawable/logo_background.xml b/example/src/main/res/drawable/logo_background.xml new file mode 100644 index 0000000..a058773 --- /dev/null +++ b/example/src/main/res/drawable/logo_background.xml @@ -0,0 +1,16 @@ + + + + + + + + + + diff --git a/example/src/main/res/font/poppins.xml b/example/src/main/res/font/poppins.xml new file mode 100644 index 0000000..de125bd --- /dev/null +++ b/example/src/main/res/font/poppins.xml @@ -0,0 +1,61 @@ + + + + + + + + + + + + diff --git a/example/src/main/res/font/poppins_bold.ttf b/example/src/main/res/font/poppins_bold.ttf new file mode 100644 index 0000000..00559ee Binary files /dev/null and b/example/src/main/res/font/poppins_bold.ttf differ diff --git a/example/src/main/res/font/poppins_extrabold.ttf b/example/src/main/res/font/poppins_extrabold.ttf new file mode 100644 index 0000000..df70936 Binary files /dev/null and b/example/src/main/res/font/poppins_extrabold.ttf differ diff --git a/example/src/main/res/font/poppins_extralight.ttf b/example/src/main/res/font/poppins_extralight.ttf new file mode 100644 index 0000000..e76ec69 Binary files /dev/null and b/example/src/main/res/font/poppins_extralight.ttf differ diff --git a/example/src/main/res/font/poppins_light.ttf b/example/src/main/res/font/poppins_light.ttf new file mode 100644 index 0000000..bc36bcc Binary files /dev/null and b/example/src/main/res/font/poppins_light.ttf differ diff --git a/example/src/main/res/font/poppins_medium.ttf b/example/src/main/res/font/poppins_medium.ttf new file mode 100644 index 0000000..6bcdcc2 Binary files /dev/null and b/example/src/main/res/font/poppins_medium.ttf differ diff --git a/example/src/main/res/font/poppins_regular.ttf b/example/src/main/res/font/poppins_regular.ttf new file mode 100644 index 0000000..9f0c71b Binary files /dev/null and b/example/src/main/res/font/poppins_regular.ttf differ diff --git a/example/src/main/res/font/poppins_semibold.ttf b/example/src/main/res/font/poppins_semibold.ttf new file mode 100644 index 0000000..74c726e Binary files /dev/null and b/example/src/main/res/font/poppins_semibold.ttf differ diff --git a/example/src/main/res/font/poppins_thin.ttf b/example/src/main/res/font/poppins_thin.ttf new file mode 100644 index 0000000..03e7366 Binary files /dev/null and b/example/src/main/res/font/poppins_thin.ttf differ diff --git a/example/src/main/res/layout/activity_main.xml b/example/src/main/res/layout/activity_main.xml new file mode 100644 index 0000000..9c1bf85 --- /dev/null +++ b/example/src/main/res/layout/activity_main.xml @@ -0,0 +1,188 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/example/src/main/res/layout/layout_item.xml b/example/src/main/res/layout/layout_item.xml new file mode 100644 index 0000000..c34db99 --- /dev/null +++ b/example/src/main/res/layout/layout_item.xml @@ -0,0 +1,54 @@ + + + + + + + + + + diff --git a/example/src/main/res/mipmap-anydpi-v26/ic_launcher.xml b/example/src/main/res/mipmap-anydpi-v26/ic_launcher.xml new file mode 100644 index 0000000..ac07c2f --- /dev/null +++ b/example/src/main/res/mipmap-anydpi-v26/ic_launcher.xml @@ -0,0 +1,6 @@ + + + + + + diff --git a/example/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml b/example/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml new file mode 100644 index 0000000..ac07c2f --- /dev/null +++ b/example/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml @@ -0,0 +1,6 @@ + + + + + + diff --git a/example/src/main/res/mipmap-hdpi/ic_launcher.webp b/example/src/main/res/mipmap-hdpi/ic_launcher.webp new file mode 100644 index 0000000..d728ab2 Binary files /dev/null and b/example/src/main/res/mipmap-hdpi/ic_launcher.webp differ diff --git a/example/src/main/res/mipmap-hdpi/ic_launcher_round.webp b/example/src/main/res/mipmap-hdpi/ic_launcher_round.webp new file mode 100644 index 0000000..c0d9a75 Binary files /dev/null and b/example/src/main/res/mipmap-hdpi/ic_launcher_round.webp differ diff --git a/example/src/main/res/mipmap-mdpi/ic_launcher.webp b/example/src/main/res/mipmap-mdpi/ic_launcher.webp new file mode 100644 index 0000000..ebff995 Binary files /dev/null and b/example/src/main/res/mipmap-mdpi/ic_launcher.webp differ diff --git a/example/src/main/res/mipmap-mdpi/ic_launcher_round.webp b/example/src/main/res/mipmap-mdpi/ic_launcher_round.webp new file mode 100644 index 0000000..7203d68 Binary files /dev/null and b/example/src/main/res/mipmap-mdpi/ic_launcher_round.webp differ diff --git a/example/src/main/res/mipmap-xhdpi/ic_launcher.webp b/example/src/main/res/mipmap-xhdpi/ic_launcher.webp new file mode 100644 index 0000000..88e3d73 Binary files /dev/null and b/example/src/main/res/mipmap-xhdpi/ic_launcher.webp differ diff --git a/example/src/main/res/mipmap-xhdpi/ic_launcher_round.webp b/example/src/main/res/mipmap-xhdpi/ic_launcher_round.webp new file mode 100644 index 0000000..6b58fc4 Binary files /dev/null and b/example/src/main/res/mipmap-xhdpi/ic_launcher_round.webp differ diff --git a/example/src/main/res/mipmap-xxhdpi/ic_launcher.webp b/example/src/main/res/mipmap-xxhdpi/ic_launcher.webp new file mode 100644 index 0000000..c1b5938 Binary files /dev/null and b/example/src/main/res/mipmap-xxhdpi/ic_launcher.webp differ diff --git a/example/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp b/example/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp new file mode 100644 index 0000000..37df203 Binary files /dev/null and b/example/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp differ diff --git a/example/src/main/res/mipmap-xxxhdpi/ic_launcher.webp b/example/src/main/res/mipmap-xxxhdpi/ic_launcher.webp new file mode 100644 index 0000000..f08e20d Binary files /dev/null and b/example/src/main/res/mipmap-xxxhdpi/ic_launcher.webp differ diff --git a/example/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp b/example/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp new file mode 100644 index 0000000..774110b Binary files /dev/null and b/example/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp differ diff --git a/example/src/main/res/values-v23/themes.xml b/example/src/main/res/values-v23/themes.xml new file mode 100644 index 0000000..69a940e --- /dev/null +++ b/example/src/main/res/values-v23/themes.xml @@ -0,0 +1,7 @@ + + + + + diff --git a/example/src/main/res/values-v31/themes.xml b/example/src/main/res/values-v31/themes.xml new file mode 100644 index 0000000..86640b8 --- /dev/null +++ b/example/src/main/res/values-v31/themes.xml @@ -0,0 +1,10 @@ + + + + + diff --git a/example/src/main/res/values/colors.xml b/example/src/main/res/values/colors.xml new file mode 100644 index 0000000..54d09ed --- /dev/null +++ b/example/src/main/res/values/colors.xml @@ -0,0 +1,13 @@ + + + #F4F6FB + + #D1E0FF + + #EF4F5F + #FAD975 + #BBD959 + #3AB757 + + #1F1F1F + diff --git a/example/src/main/res/values/strings.xml b/example/src/main/res/values/strings.xml new file mode 100644 index 0000000..76c8498 --- /dev/null +++ b/example/src/main/res/values/strings.xml @@ -0,0 +1,11 @@ + + Droid Dex + + Droid Dex + + - + + Individual + Aggregate + Weighted Aggregate + diff --git a/example/src/main/res/values/themes.xml b/example/src/main/res/values/themes.xml new file mode 100644 index 0000000..a14d7e4 --- /dev/null +++ b/example/src/main/res/values/themes.xml @@ -0,0 +1,52 @@ + + + + + + + + + + + + + + + + + + + + + diff --git a/gradle.properties b/gradle.properties new file mode 100644 index 0000000..b4f52f6 --- /dev/null +++ b/gradle.properties @@ -0,0 +1,62 @@ +# Project-wide Gradle settings. +# IDE (e.g. Android Studio) users: +# Gradle settings configured through the IDE *will override* +# any settings specified in this file. +# For more details on how to configure your build environment visit +# http://www.gradle.org/docs/current/userguide/build_environment.html +# Specifies the JVM arguments used for the daemon process. +# The setting is particularly useful for tweaking memory settings. +org.gradle.jvmargs=-Xmx2048m -Dfile.encoding=UTF-8 +# When configured, Gradle will run in incubating parallel mode. +# This option should only be used with decoupled projects. For more details, visit +# https://developer.android.com/r/tools/gradle-multi-project-decoupled-projects +# org.gradle.parallel=true +# AndroidX package structure to make it clearer which packages are bundled with the +# Android operating system, and which are packaged with your app's APK +# https://developer.android.com/topic/libraries/support-library/androidx-rn +android.useAndroidX=true +# Kotlin code style for this project: "official" or "obsolete": +kotlin.code.style=official + +# Gradle Properties https://docs.gradle.org/current/userguide/build_environment.html#sec:configuring_jvm_memory + +# This line enables Gradle's caching mechanism, which caches build information to speed up subsequent builds. +org.gradle.caching=true +# This line disables debugging information for Gradle's caching mechanism. +org.gradle.caching.debug=false + +# This line enables the Gradle Daemon, which is a long-lived background process that can speed up Gradle builds by keeping parts of the build process in memory. +org.gradle.daemon=true + +# This line enables parallel execution of tasks in Gradle, allowing multiple tasks to run concurrently, which can speed up the build process. +org.gradle.parallel=true + +# This line sets the warning mode to "summary" which means that Gradle will provide a summary of warnings during the build. +org.gradle.warning.mode=summary + +# Enabled Configuration Cache +org.gradle.configuration-cache=true +org.gradle.configuration-cache-problems=warn + + +# Maven Publishing +GROUP=com.blinkit.kits +POM_ARTIFACT_ID=droid-dex +VERSION_NAME=1.0.0 + +POM_NAME=Droid Dex +POM_DESCRIPTION=Classification and Analysis of Android Device Performance +POM_INCEPTION_YEAR=2024 +POM_URL=https://github.com/grofers/droid-dex + +POM_LICENSE_NAME=The Apache Software License, Version 2.0 +POM_LICENSE_URL=https://www.apache.org/licenses/LICENSE-2.0.txt +POM_LICENSE_DIST=repo + +POM_SCM_URL=https://github.com/grofers/droid-dex +POM_SCM_CONNECTION=scm:git:git://github.com/grofers/droid-dex.git +POM_SCM_DEV_CONNECTION=scm:git:ssh://git@github.com/grofers/droid-dex.git + +POM_DEVELOPER_ID=Blinkit +POM_DEVELOPER_NAME=Blink Commerce Private Limited (formerly known as Grofers India Private Limited) +POM_DEVELOPER_URL=https://github.com/grofers/ diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml new file mode 100644 index 0000000..26d245c --- /dev/null +++ b/gradle/libs.versions.toml @@ -0,0 +1,58 @@ +[versions] +java = "11" + +minSdk = "21" +targetSdk = "34" +compileSdk = "34" + +agp = "7.4.2" +kotlin = "1.8.21" +maven-publish = "0.27.0" + +timber = "5.0.1" + +core-ktx = "1.7.0" +app-compat = "1.6.0" + +material = "1.9.0" +constraint-layout = "2.1.4" + +kotlinx-coroutines = "1.6.4" + +lifecycle = "2.5.1" + +core-performance = "1.0.0-alpha02" + + +[libraries] +timber = { group = "com.jakewharton.timber", name = "timber", version.ref = "timber" } + +androidx-core-ktx = { group = "androidx.core", name = "core-ktx", version.ref = "core-ktx" } +androidx-appcompat = { group = "androidx.appcompat", name = "appcompat", version.ref = "app-compat" } + +material = { group = "com.google.android.material", name = "material", version.ref = "material" } +constraint-layout = { group = "androidx.constraintlayout", name = "constraintlayout", version.ref = "constraint-layout" } + +kotlinx-coroutines-android = { group = "org.jetbrains.kotlinx", name = "kotlinx-coroutines-android", version.ref = "kotlinx-coroutines" } + +androidx-lifecycle-process = { group = "androidx.lifecycle", name = "lifecycle-process", version.ref = "lifecycle" } +androidx-lifecycle-runtime-ktx = { group = "androidx.lifecycle", name = "lifecycle-runtime-ktx", version.ref = "lifecycle" } + +androidx-core-performance = { group = "androidx.core", name = "core-performance", version.ref = "core-performance" } + + +[bundles] +core = ["androidx-core-ktx", "androidx-appcompat"] + +ui = ["material", "constraint-layout"] + +lifecycle = ["androidx-lifecycle-process", "androidx-lifecycle-runtime-ktx"] + + +[plugins] +android-application = { id = "com.android.application", version.ref = "agp" } +android-library = { id = "com.android.library", version.ref = "agp" } + +jetbrains-kotlin-android = { id = "org.jetbrains.kotlin.android", version.ref = "kotlin" } + +maven-publish = { id = "com.vanniktech.maven.publish", version.ref = "maven-publish" } diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar new file mode 100644 index 0000000..e708b1c Binary files /dev/null and b/gradle/wrapper/gradle-wrapper.jar differ diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 0000000..ccbffef --- /dev/null +++ b/gradle/wrapper/gradle-wrapper.properties @@ -0,0 +1,6 @@ +#Wed Jan 03 19:34:15 IST 2024 +distributionBase=GRADLE_USER_HOME +distributionPath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-8.4-bin.zip +zipStoreBase=GRADLE_USER_HOME +zipStorePath=wrapper/dists diff --git a/gradlew b/gradlew new file mode 100755 index 0000000..4f906e0 --- /dev/null +++ b/gradlew @@ -0,0 +1,185 @@ +#!/usr/bin/env sh + +# +# Copyright 2015 the original author or authors. +# +# 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 +# +# https://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. +# + +############################################################################## +## +## Gradle start up script for UN*X +## +############################################################################## + +# Attempt to set APP_HOME +# Resolve links: $0 may be a link +PRG="$0" +# Need this for relative symlinks. +while [ -h "$PRG" ] ; do + ls=`ls -ld "$PRG"` + link=`expr "$ls" : '.*-> \(.*\)$'` + if expr "$link" : '/.*' > /dev/null; then + PRG="$link" + else + PRG=`dirname "$PRG"`"/$link" + fi +done +SAVED="`pwd`" +cd "`dirname \"$PRG\"`/" >/dev/null +APP_HOME="`pwd -P`" +cd "$SAVED" >/dev/null + +APP_NAME="Gradle" +APP_BASE_NAME=`basename "$0"` + +# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' + +# Use the maximum available, or set MAX_FD != -1 to use that value. +MAX_FD="maximum" + +warn () { + echo "$*" +} + +die () { + echo + echo "$*" + echo + exit 1 +} + +# OS specific support (must be 'true' or 'false'). +cygwin=false +msys=false +darwin=false +nonstop=false +case "`uname`" in + CYGWIN* ) + cygwin=true + ;; + Darwin* ) + darwin=true + ;; + MINGW* ) + msys=true + ;; + NONSTOP* ) + nonstop=true + ;; +esac + +CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar + + +# Determine the Java command to use to start the JVM. +if [ -n "$JAVA_HOME" ] ; then + if [ -x "$JAVA_HOME/jre/sh/java" ] ; then + # IBM's JDK on AIX uses strange locations for the executables + JAVACMD="$JAVA_HOME/jre/sh/java" + else + JAVACMD="$JAVA_HOME/bin/java" + fi + if [ ! -x "$JAVACMD" ] ; then + die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." + fi +else + JAVACMD="java" + which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." +fi + +# Increase the maximum file descriptors if we can. +if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then + MAX_FD_LIMIT=`ulimit -H -n` + if [ $? -eq 0 ] ; then + if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then + MAX_FD="$MAX_FD_LIMIT" + fi + ulimit -n $MAX_FD + if [ $? -ne 0 ] ; then + warn "Could not set maximum file descriptor limit: $MAX_FD" + fi + else + warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" + fi +fi + +# For Darwin, add options to specify how the application appears in the dock +if $darwin; then + GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" +fi + +# For Cygwin or MSYS, switch paths to Windows format before running java +if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then + APP_HOME=`cygpath --path --mixed "$APP_HOME"` + CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` + + JAVACMD=`cygpath --unix "$JAVACMD"` + + # We build the pattern for arguments to be converted via cygpath + ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` + SEP="" + for dir in $ROOTDIRSRAW ; do + ROOTDIRS="$ROOTDIRS$SEP$dir" + SEP="|" + done + OURCYGPATTERN="(^($ROOTDIRS))" + # Add a user-defined pattern to the cygpath arguments + if [ "$GRADLE_CYGPATTERN" != "" ] ; then + OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" + fi + # Now convert the arguments - kludge to limit ourselves to /bin/sh + i=0 + for arg in "$@" ; do + CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` + CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option + + if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition + eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` + else + eval `echo args$i`="\"$arg\"" + fi + i=`expr $i + 1` + done + case $i in + 0) set -- ;; + 1) set -- "$args0" ;; + 2) set -- "$args0" "$args1" ;; + 3) set -- "$args0" "$args1" "$args2" ;; + 4) set -- "$args0" "$args1" "$args2" "$args3" ;; + 5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; + 6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; + 7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; + 8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; + 9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; + esac +fi + +# Escape application args +save () { + for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done + echo " " +} +APP_ARGS=`save "$@"` + +# Collect all arguments for the java command, following the shell quoting and substitution rules +eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS" + +exec "$JAVACMD" "$@" diff --git a/gradlew.bat b/gradlew.bat new file mode 100644 index 0000000..107acd3 --- /dev/null +++ b/gradlew.bat @@ -0,0 +1,89 @@ +@rem +@rem Copyright 2015 the original author or authors. +@rem +@rem Licensed under the Apache License, Version 2.0 (the "License"); +@rem you may not use this file except in compliance with the License. +@rem You may obtain a copy of the License at +@rem +@rem https://www.apache.org/licenses/LICENSE-2.0 +@rem +@rem Unless required by applicable law or agreed to in writing, software +@rem distributed under the License is distributed on an "AS IS" BASIS, +@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +@rem See the License for the specific language governing permissions and +@rem limitations under the License. +@rem + +@if "%DEBUG%" == "" @echo off +@rem ########################################################################## +@rem +@rem Gradle startup script for Windows +@rem +@rem ########################################################################## + +@rem Set local scope for the variables with windows NT shell +if "%OS%"=="Windows_NT" setlocal + +set DIRNAME=%~dp0 +if "%DIRNAME%" == "" set DIRNAME=. +set APP_BASE_NAME=%~n0 +set APP_HOME=%DIRNAME% + +@rem Resolve any "." and ".." in APP_HOME to make it shorter. +for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi + +@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" + +@rem Find java.exe +if defined JAVA_HOME goto findJavaFromJavaHome + +set JAVA_EXE=java.exe +%JAVA_EXE% -version >NUL 2>&1 +if "%ERRORLEVEL%" == "0" goto execute + +echo. +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:findJavaFromJavaHome +set JAVA_HOME=%JAVA_HOME:"=% +set JAVA_EXE=%JAVA_HOME%/bin/java.exe + +if exist "%JAVA_EXE%" goto execute + +echo. +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:execute +@rem Setup the command line + +set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar + + +@rem Execute Gradle +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* + +:end +@rem End local scope for the variables with windows NT shell +if "%ERRORLEVEL%"=="0" goto mainEnd + +:fail +rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of +rem the _cmd.exe /c_ return code! +if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 +exit /b 1 + +:mainEnd +if "%OS%"=="Windows_NT" endlocal + +:omega diff --git a/localPublish.sh b/localPublish.sh new file mode 100755 index 0000000..33a2927 --- /dev/null +++ b/localPublish.sh @@ -0,0 +1,9 @@ +#!/bin/bash + +./gradlew clean +./gradlew droid-dex:assembleRelease + +echo -e "\n\nPublishing to LOCAL. BEWARE. Waiting for 2 seconds before continuing\n\n" +sleep 2 + +./gradlew droid-dex:publishMavenPublicationToMavenLocal diff --git a/remotePublish.sh b/remotePublish.sh new file mode 100755 index 0000000..6fa4828 --- /dev/null +++ b/remotePublish.sh @@ -0,0 +1,9 @@ +#!/bin/bash + +./gradlew clean +./gradlew droid-dex:assembleRelease + +echo -e "\n\nPublishing to PRODUCTION. BEWARE. Waiting for 5 seconds before continuing\n\n" +sleep 5 + +./gradlew droid-dex:publishMavenPublicationToBlinkitRepository diff --git a/settings.gradle.kts b/settings.gradle.kts new file mode 100644 index 0000000..c55c660 --- /dev/null +++ b/settings.gradle.kts @@ -0,0 +1,23 @@ +@file:Suppress("UnstableApiUsage") + +pluginManagement { + repositories { + gradlePluginPortal() + google() + mavenCentral() + } +} + +dependencyResolutionManagement { + repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS) + repositories { + google() + mavenCentral() + } +} + +rootProject.name = "Droid Dex" + +include(":droid-dex") + +include(":example")