diff --git a/.classpath b/.classpath
new file mode 100644
index 0000000..9ffc2b7
--- /dev/null
+++ b/.classpath
@@ -0,0 +1,9 @@
+
+
+
+
+
+
+
+
+
diff --git a/.gitignore b/.gitignore
index 0f182a0..a22392a 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,6 +1,2 @@
-*.class
-
-# Package Files #
-*.jar
-*.war
-*.ear
+/bin
+/gen
diff --git a/.project b/.project
new file mode 100644
index 0000000..a12611c
--- /dev/null
+++ b/.project
@@ -0,0 +1,33 @@
+
+
+ XposedAppSettings
+
+
+
+
+
+ com.android.ide.eclipse.adt.ResourceManagerBuilder
+
+
+
+
+ com.android.ide.eclipse.adt.PreCompilerBuilder
+
+
+
+
+ org.eclipse.jdt.core.javabuilder
+
+
+
+
+ com.android.ide.eclipse.adt.ApkBuilder
+
+
+
+
+
+ com.android.ide.eclipse.adt.AndroidNature
+ org.eclipse.jdt.core.javanature
+
+
diff --git a/.settings/org.eclipse.jdt.core.prefs b/.settings/org.eclipse.jdt.core.prefs
new file mode 100644
index 0000000..73673c6
--- /dev/null
+++ b/.settings/org.eclipse.jdt.core.prefs
@@ -0,0 +1,291 @@
+eclipse.preferences.version=1
+org.eclipse.jdt.core.compiler.codegen.inlineJsrBytecode=enabled
+org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.6
+org.eclipse.jdt.core.compiler.codegen.unusedLocal=preserve
+org.eclipse.jdt.core.compiler.compliance=1.6
+org.eclipse.jdt.core.compiler.debug.lineNumber=generate
+org.eclipse.jdt.core.compiler.debug.localVariable=generate
+org.eclipse.jdt.core.compiler.debug.sourceFile=generate
+org.eclipse.jdt.core.compiler.problem.assertIdentifier=error
+org.eclipse.jdt.core.compiler.problem.enumIdentifier=error
+org.eclipse.jdt.core.compiler.source=1.6
+org.eclipse.jdt.core.formatter.align_type_members_on_columns=false
+org.eclipse.jdt.core.formatter.alignment_for_arguments_in_allocation_expression=16
+org.eclipse.jdt.core.formatter.alignment_for_arguments_in_annotation=0
+org.eclipse.jdt.core.formatter.alignment_for_arguments_in_enum_constant=16
+org.eclipse.jdt.core.formatter.alignment_for_arguments_in_explicit_constructor_call=16
+org.eclipse.jdt.core.formatter.alignment_for_arguments_in_method_invocation=16
+org.eclipse.jdt.core.formatter.alignment_for_arguments_in_qualified_allocation_expression=16
+org.eclipse.jdt.core.formatter.alignment_for_assignment=0
+org.eclipse.jdt.core.formatter.alignment_for_binary_expression=16
+org.eclipse.jdt.core.formatter.alignment_for_compact_if=16
+org.eclipse.jdt.core.formatter.alignment_for_conditional_expression=80
+org.eclipse.jdt.core.formatter.alignment_for_enum_constants=0
+org.eclipse.jdt.core.formatter.alignment_for_expressions_in_array_initializer=16
+org.eclipse.jdt.core.formatter.alignment_for_method_declaration=0
+org.eclipse.jdt.core.formatter.alignment_for_multiple_fields=16
+org.eclipse.jdt.core.formatter.alignment_for_parameters_in_constructor_declaration=16
+org.eclipse.jdt.core.formatter.alignment_for_parameters_in_method_declaration=0
+org.eclipse.jdt.core.formatter.alignment_for_resources_in_try=80
+org.eclipse.jdt.core.formatter.alignment_for_selector_in_method_invocation=16
+org.eclipse.jdt.core.formatter.alignment_for_superclass_in_type_declaration=16
+org.eclipse.jdt.core.formatter.alignment_for_superinterfaces_in_enum_declaration=16
+org.eclipse.jdt.core.formatter.alignment_for_superinterfaces_in_type_declaration=16
+org.eclipse.jdt.core.formatter.alignment_for_throws_clause_in_constructor_declaration=16
+org.eclipse.jdt.core.formatter.alignment_for_throws_clause_in_method_declaration=0
+org.eclipse.jdt.core.formatter.alignment_for_union_type_in_multicatch=16
+org.eclipse.jdt.core.formatter.blank_lines_after_imports=1
+org.eclipse.jdt.core.formatter.blank_lines_after_package=1
+org.eclipse.jdt.core.formatter.blank_lines_before_field=0
+org.eclipse.jdt.core.formatter.blank_lines_before_first_class_body_declaration=0
+org.eclipse.jdt.core.formatter.blank_lines_before_imports=1
+org.eclipse.jdt.core.formatter.blank_lines_before_member_type=1
+org.eclipse.jdt.core.formatter.blank_lines_before_method=1
+org.eclipse.jdt.core.formatter.blank_lines_before_new_chunk=1
+org.eclipse.jdt.core.formatter.blank_lines_before_package=0
+org.eclipse.jdt.core.formatter.blank_lines_between_import_groups=1
+org.eclipse.jdt.core.formatter.blank_lines_between_type_declarations=1
+org.eclipse.jdt.core.formatter.brace_position_for_annotation_type_declaration=end_of_line
+org.eclipse.jdt.core.formatter.brace_position_for_anonymous_type_declaration=end_of_line
+org.eclipse.jdt.core.formatter.brace_position_for_array_initializer=end_of_line
+org.eclipse.jdt.core.formatter.brace_position_for_block=end_of_line
+org.eclipse.jdt.core.formatter.brace_position_for_block_in_case=end_of_line
+org.eclipse.jdt.core.formatter.brace_position_for_constructor_declaration=end_of_line
+org.eclipse.jdt.core.formatter.brace_position_for_enum_constant=end_of_line
+org.eclipse.jdt.core.formatter.brace_position_for_enum_declaration=end_of_line
+org.eclipse.jdt.core.formatter.brace_position_for_method_declaration=end_of_line
+org.eclipse.jdt.core.formatter.brace_position_for_switch=end_of_line
+org.eclipse.jdt.core.formatter.brace_position_for_type_declaration=end_of_line
+org.eclipse.jdt.core.formatter.comment.clear_blank_lines_in_block_comment=false
+org.eclipse.jdt.core.formatter.comment.clear_blank_lines_in_javadoc_comment=false
+org.eclipse.jdt.core.formatter.comment.format_block_comments=true
+org.eclipse.jdt.core.formatter.comment.format_header=false
+org.eclipse.jdt.core.formatter.comment.format_html=true
+org.eclipse.jdt.core.formatter.comment.format_javadoc_comments=true
+org.eclipse.jdt.core.formatter.comment.format_line_comments=true
+org.eclipse.jdt.core.formatter.comment.format_source_code=true
+org.eclipse.jdt.core.formatter.comment.indent_parameter_description=true
+org.eclipse.jdt.core.formatter.comment.indent_root_tags=true
+org.eclipse.jdt.core.formatter.comment.insert_new_line_before_root_tags=insert
+org.eclipse.jdt.core.formatter.comment.insert_new_line_for_parameter=insert
+org.eclipse.jdt.core.formatter.comment.line_length=80
+org.eclipse.jdt.core.formatter.comment.new_lines_at_block_boundaries=true
+org.eclipse.jdt.core.formatter.comment.new_lines_at_javadoc_boundaries=true
+org.eclipse.jdt.core.formatter.comment.preserve_white_space_between_code_and_line_comments=false
+org.eclipse.jdt.core.formatter.compact_else_if=true
+org.eclipse.jdt.core.formatter.continuation_indentation=2
+org.eclipse.jdt.core.formatter.continuation_indentation_for_array_initializer=2
+org.eclipse.jdt.core.formatter.disabling_tag=@formatter\:off
+org.eclipse.jdt.core.formatter.enabling_tag=@formatter\:on
+org.eclipse.jdt.core.formatter.format_guardian_clause_on_one_line=false
+org.eclipse.jdt.core.formatter.format_line_comment_starting_on_first_column=true
+org.eclipse.jdt.core.formatter.indent_body_declarations_compare_to_annotation_declaration_header=true
+org.eclipse.jdt.core.formatter.indent_body_declarations_compare_to_enum_constant_header=true
+org.eclipse.jdt.core.formatter.indent_body_declarations_compare_to_enum_declaration_header=true
+org.eclipse.jdt.core.formatter.indent_body_declarations_compare_to_type_header=true
+org.eclipse.jdt.core.formatter.indent_breaks_compare_to_cases=true
+org.eclipse.jdt.core.formatter.indent_empty_lines=false
+org.eclipse.jdt.core.formatter.indent_statements_compare_to_block=true
+org.eclipse.jdt.core.formatter.indent_statements_compare_to_body=true
+org.eclipse.jdt.core.formatter.indent_switchstatements_compare_to_cases=true
+org.eclipse.jdt.core.formatter.indent_switchstatements_compare_to_switch=false
+org.eclipse.jdt.core.formatter.indentation.size=4
+org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_field=insert
+org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_local_variable=insert
+org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_method=insert
+org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_package=insert
+org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_parameter=do not insert
+org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_type=insert
+org.eclipse.jdt.core.formatter.insert_new_line_after_label=do not insert
+org.eclipse.jdt.core.formatter.insert_new_line_after_opening_brace_in_array_initializer=do not insert
+org.eclipse.jdt.core.formatter.insert_new_line_at_end_of_file_if_missing=do not insert
+org.eclipse.jdt.core.formatter.insert_new_line_before_catch_in_try_statement=do not insert
+org.eclipse.jdt.core.formatter.insert_new_line_before_closing_brace_in_array_initializer=do not insert
+org.eclipse.jdt.core.formatter.insert_new_line_before_else_in_if_statement=do not insert
+org.eclipse.jdt.core.formatter.insert_new_line_before_finally_in_try_statement=do not insert
+org.eclipse.jdt.core.formatter.insert_new_line_before_while_in_do_statement=do not insert
+org.eclipse.jdt.core.formatter.insert_new_line_in_empty_annotation_declaration=insert
+org.eclipse.jdt.core.formatter.insert_new_line_in_empty_anonymous_type_declaration=insert
+org.eclipse.jdt.core.formatter.insert_new_line_in_empty_block=insert
+org.eclipse.jdt.core.formatter.insert_new_line_in_empty_enum_constant=insert
+org.eclipse.jdt.core.formatter.insert_new_line_in_empty_enum_declaration=insert
+org.eclipse.jdt.core.formatter.insert_new_line_in_empty_method_body=insert
+org.eclipse.jdt.core.formatter.insert_new_line_in_empty_type_declaration=insert
+org.eclipse.jdt.core.formatter.insert_space_after_and_in_type_parameter=insert
+org.eclipse.jdt.core.formatter.insert_space_after_assignment_operator=insert
+org.eclipse.jdt.core.formatter.insert_space_after_at_in_annotation=do not insert
+org.eclipse.jdt.core.formatter.insert_space_after_at_in_annotation_type_declaration=do not insert
+org.eclipse.jdt.core.formatter.insert_space_after_binary_operator=insert
+org.eclipse.jdt.core.formatter.insert_space_after_closing_angle_bracket_in_type_arguments=insert
+org.eclipse.jdt.core.formatter.insert_space_after_closing_angle_bracket_in_type_parameters=insert
+org.eclipse.jdt.core.formatter.insert_space_after_closing_brace_in_block=insert
+org.eclipse.jdt.core.formatter.insert_space_after_closing_paren_in_cast=insert
+org.eclipse.jdt.core.formatter.insert_space_after_colon_in_assert=insert
+org.eclipse.jdt.core.formatter.insert_space_after_colon_in_case=insert
+org.eclipse.jdt.core.formatter.insert_space_after_colon_in_conditional=insert
+org.eclipse.jdt.core.formatter.insert_space_after_colon_in_for=insert
+org.eclipse.jdt.core.formatter.insert_space_after_colon_in_labeled_statement=insert
+org.eclipse.jdt.core.formatter.insert_space_after_comma_in_allocation_expression=insert
+org.eclipse.jdt.core.formatter.insert_space_after_comma_in_annotation=insert
+org.eclipse.jdt.core.formatter.insert_space_after_comma_in_array_initializer=insert
+org.eclipse.jdt.core.formatter.insert_space_after_comma_in_constructor_declaration_parameters=insert
+org.eclipse.jdt.core.formatter.insert_space_after_comma_in_constructor_declaration_throws=insert
+org.eclipse.jdt.core.formatter.insert_space_after_comma_in_enum_constant_arguments=insert
+org.eclipse.jdt.core.formatter.insert_space_after_comma_in_enum_declarations=insert
+org.eclipse.jdt.core.formatter.insert_space_after_comma_in_explicitconstructorcall_arguments=insert
+org.eclipse.jdt.core.formatter.insert_space_after_comma_in_for_increments=insert
+org.eclipse.jdt.core.formatter.insert_space_after_comma_in_for_inits=insert
+org.eclipse.jdt.core.formatter.insert_space_after_comma_in_method_declaration_parameters=insert
+org.eclipse.jdt.core.formatter.insert_space_after_comma_in_method_declaration_throws=insert
+org.eclipse.jdt.core.formatter.insert_space_after_comma_in_method_invocation_arguments=insert
+org.eclipse.jdt.core.formatter.insert_space_after_comma_in_multiple_field_declarations=insert
+org.eclipse.jdt.core.formatter.insert_space_after_comma_in_multiple_local_declarations=insert
+org.eclipse.jdt.core.formatter.insert_space_after_comma_in_parameterized_type_reference=insert
+org.eclipse.jdt.core.formatter.insert_space_after_comma_in_superinterfaces=insert
+org.eclipse.jdt.core.formatter.insert_space_after_comma_in_type_arguments=insert
+org.eclipse.jdt.core.formatter.insert_space_after_comma_in_type_parameters=insert
+org.eclipse.jdt.core.formatter.insert_space_after_ellipsis=insert
+org.eclipse.jdt.core.formatter.insert_space_after_opening_angle_bracket_in_parameterized_type_reference=do not insert
+org.eclipse.jdt.core.formatter.insert_space_after_opening_angle_bracket_in_type_arguments=do not insert
+org.eclipse.jdt.core.formatter.insert_space_after_opening_angle_bracket_in_type_parameters=do not insert
+org.eclipse.jdt.core.formatter.insert_space_after_opening_brace_in_array_initializer=insert
+org.eclipse.jdt.core.formatter.insert_space_after_opening_bracket_in_array_allocation_expression=do not insert
+org.eclipse.jdt.core.formatter.insert_space_after_opening_bracket_in_array_reference=do not insert
+org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_annotation=do not insert
+org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_cast=do not insert
+org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_catch=do not insert
+org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_constructor_declaration=do not insert
+org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_enum_constant=do not insert
+org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_for=do not insert
+org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_if=do not insert
+org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_method_declaration=do not insert
+org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_method_invocation=do not insert
+org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_parenthesized_expression=do not insert
+org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_switch=do not insert
+org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_synchronized=do not insert
+org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_try=do not insert
+org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_while=do not insert
+org.eclipse.jdt.core.formatter.insert_space_after_postfix_operator=do not insert
+org.eclipse.jdt.core.formatter.insert_space_after_prefix_operator=do not insert
+org.eclipse.jdt.core.formatter.insert_space_after_question_in_conditional=insert
+org.eclipse.jdt.core.formatter.insert_space_after_question_in_wildcard=do not insert
+org.eclipse.jdt.core.formatter.insert_space_after_semicolon_in_for=insert
+org.eclipse.jdt.core.formatter.insert_space_after_semicolon_in_try_resources=insert
+org.eclipse.jdt.core.formatter.insert_space_after_unary_operator=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_and_in_type_parameter=insert
+org.eclipse.jdt.core.formatter.insert_space_before_assignment_operator=insert
+org.eclipse.jdt.core.formatter.insert_space_before_at_in_annotation_type_declaration=insert
+org.eclipse.jdt.core.formatter.insert_space_before_binary_operator=insert
+org.eclipse.jdt.core.formatter.insert_space_before_closing_angle_bracket_in_parameterized_type_reference=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_closing_angle_bracket_in_type_arguments=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_closing_angle_bracket_in_type_parameters=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_closing_brace_in_array_initializer=insert
+org.eclipse.jdt.core.formatter.insert_space_before_closing_bracket_in_array_allocation_expression=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_closing_bracket_in_array_reference=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_annotation=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_cast=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_catch=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_constructor_declaration=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_enum_constant=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_for=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_if=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_method_declaration=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_method_invocation=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_parenthesized_expression=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_switch=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_synchronized=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_try=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_while=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_colon_in_assert=insert
+org.eclipse.jdt.core.formatter.insert_space_before_colon_in_case=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_colon_in_conditional=insert
+org.eclipse.jdt.core.formatter.insert_space_before_colon_in_default=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_colon_in_for=insert
+org.eclipse.jdt.core.formatter.insert_space_before_colon_in_labeled_statement=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_comma_in_allocation_expression=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_comma_in_annotation=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_comma_in_array_initializer=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_comma_in_constructor_declaration_parameters=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_comma_in_constructor_declaration_throws=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_comma_in_enum_constant_arguments=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_comma_in_enum_declarations=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_comma_in_explicitconstructorcall_arguments=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_comma_in_for_increments=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_comma_in_for_inits=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_comma_in_method_declaration_parameters=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_comma_in_method_declaration_throws=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_comma_in_method_invocation_arguments=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_comma_in_multiple_field_declarations=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_comma_in_multiple_local_declarations=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_comma_in_parameterized_type_reference=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_comma_in_superinterfaces=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_comma_in_type_arguments=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_comma_in_type_parameters=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_ellipsis=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_opening_angle_bracket_in_parameterized_type_reference=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_opening_angle_bracket_in_type_arguments=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_opening_angle_bracket_in_type_parameters=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_annotation_type_declaration=insert
+org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_anonymous_type_declaration=insert
+org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_array_initializer=insert
+org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_block=insert
+org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_constructor_declaration=insert
+org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_enum_constant=insert
+org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_enum_declaration=insert
+org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_method_declaration=insert
+org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_switch=insert
+org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_type_declaration=insert
+org.eclipse.jdt.core.formatter.insert_space_before_opening_bracket_in_array_allocation_expression=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_opening_bracket_in_array_reference=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_opening_bracket_in_array_type_reference=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_annotation=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_annotation_type_member_declaration=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_catch=insert
+org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_constructor_declaration=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_enum_constant=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_for=insert
+org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_if=insert
+org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_method_declaration=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_method_invocation=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_parenthesized_expression=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_switch=insert
+org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_synchronized=insert
+org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_try=insert
+org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_while=insert
+org.eclipse.jdt.core.formatter.insert_space_before_parenthesized_expression_in_return=insert
+org.eclipse.jdt.core.formatter.insert_space_before_parenthesized_expression_in_throw=insert
+org.eclipse.jdt.core.formatter.insert_space_before_postfix_operator=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_prefix_operator=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_question_in_conditional=insert
+org.eclipse.jdt.core.formatter.insert_space_before_question_in_wildcard=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_semicolon=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_semicolon_in_for=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_semicolon_in_try_resources=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_unary_operator=do not insert
+org.eclipse.jdt.core.formatter.insert_space_between_brackets_in_array_type_reference=do not insert
+org.eclipse.jdt.core.formatter.insert_space_between_empty_braces_in_array_initializer=do not insert
+org.eclipse.jdt.core.formatter.insert_space_between_empty_brackets_in_array_allocation_expression=do not insert
+org.eclipse.jdt.core.formatter.insert_space_between_empty_parens_in_annotation_type_member_declaration=do not insert
+org.eclipse.jdt.core.formatter.insert_space_between_empty_parens_in_constructor_declaration=do not insert
+org.eclipse.jdt.core.formatter.insert_space_between_empty_parens_in_enum_constant=do not insert
+org.eclipse.jdt.core.formatter.insert_space_between_empty_parens_in_method_declaration=do not insert
+org.eclipse.jdt.core.formatter.insert_space_between_empty_parens_in_method_invocation=do not insert
+org.eclipse.jdt.core.formatter.join_lines_in_comments=true
+org.eclipse.jdt.core.formatter.join_wrapped_lines=true
+org.eclipse.jdt.core.formatter.keep_else_statement_on_same_line=false
+org.eclipse.jdt.core.formatter.keep_empty_array_initializer_on_one_line=false
+org.eclipse.jdt.core.formatter.keep_imple_if_on_one_line=false
+org.eclipse.jdt.core.formatter.keep_then_statement_on_same_line=false
+org.eclipse.jdt.core.formatter.lineSplit=9999
+org.eclipse.jdt.core.formatter.never_indent_block_comments_on_first_column=false
+org.eclipse.jdt.core.formatter.never_indent_line_comments_on_first_column=false
+org.eclipse.jdt.core.formatter.number_of_blank_lines_at_beginning_of_method_body=0
+org.eclipse.jdt.core.formatter.number_of_empty_lines_to_preserve=1
+org.eclipse.jdt.core.formatter.put_empty_statement_on_new_line=true
+org.eclipse.jdt.core.formatter.tabulation.char=tab
+org.eclipse.jdt.core.formatter.tabulation.size=4
+org.eclipse.jdt.core.formatter.use_on_off_tags=false
+org.eclipse.jdt.core.formatter.use_tabs_only_for_leading_indentations=true
+org.eclipse.jdt.core.formatter.wrap_before_binary_operator=true
+org.eclipse.jdt.core.formatter.wrap_before_or_operator_multicatch=true
+org.eclipse.jdt.core.formatter.wrap_outer_expressions_when_nested=true
diff --git a/.settings/org.eclipse.jdt.ui.prefs b/.settings/org.eclipse.jdt.ui.prefs
new file mode 100644
index 0000000..dff1113
--- /dev/null
+++ b/.settings/org.eclipse.jdt.ui.prefs
@@ -0,0 +1,3 @@
+eclipse.preferences.version=1
+formatter_profile=_Xposed
+formatter_settings_version=12
diff --git a/AndroidManifest.xml b/AndroidManifest.xml
new file mode 100644
index 0000000..de5d47a
--- /dev/null
+++ b/AndroidManifest.xml
@@ -0,0 +1,45 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/assets/xposed_init b/assets/xposed_init
new file mode 100644
index 0000000..be2c6dd
--- /dev/null
+++ b/assets/xposed_init
@@ -0,0 +1 @@
+de.robv.android.xposed.mods.appsettings.XposedMod
diff --git a/lib/XposedBridgeApi-2.0rc2.jar b/lib/XposedBridgeApi-2.0rc2.jar
new file mode 100644
index 0000000..c96d3e6
Binary files /dev/null and b/lib/XposedBridgeApi-2.0rc2.jar differ
diff --git a/project.properties b/project.properties
new file mode 100644
index 0000000..9c52cb1
--- /dev/null
+++ b/project.properties
@@ -0,0 +1,14 @@
+# This file is automatically generated by Android Tools.
+# Do not modify this file -- YOUR CHANGES WILL BE ERASED!
+#
+# This file must be checked in Version Control Systems.
+#
+# To customize properties used by the Ant build system edit
+# "ant.properties", and override values to adapt the script to your
+# project structure.
+#
+# To enable ProGuard to shrink and obfuscate your code, uncomment this (available properties: sdk.dir, user.home):
+#proguard.config=${sdk.dir}\tools\proguard\proguard-android.txt:proguard-project.txt
+
+# Project target.
+target=android-15
diff --git a/res/drawable-hdpi/ic_launcher.png b/res/drawable-hdpi/ic_launcher.png
new file mode 100644
index 0000000..bd1b530
Binary files /dev/null and b/res/drawable-hdpi/ic_launcher.png differ
diff --git a/res/drawable-hdpi/ic_menu_save.png b/res/drawable-hdpi/ic_menu_save.png
new file mode 100644
index 0000000..36fc7f4
Binary files /dev/null and b/res/drawable-hdpi/ic_menu_save.png differ
diff --git a/res/drawable-ldpi/ic_launcher.png b/res/drawable-ldpi/ic_launcher.png
new file mode 100644
index 0000000..609c16e
Binary files /dev/null and b/res/drawable-ldpi/ic_launcher.png differ
diff --git a/res/drawable-ldpi/ic_menu_save.png b/res/drawable-ldpi/ic_menu_save.png
new file mode 100644
index 0000000..ac053b4
Binary files /dev/null and b/res/drawable-ldpi/ic_menu_save.png differ
diff --git a/res/drawable-mdpi/ic_launcher.png b/res/drawable-mdpi/ic_launcher.png
new file mode 100644
index 0000000..eef08ff
Binary files /dev/null and b/res/drawable-mdpi/ic_launcher.png differ
diff --git a/res/drawable-mdpi/ic_menu_save.png b/res/drawable-mdpi/ic_menu_save.png
new file mode 100644
index 0000000..5f66864
Binary files /dev/null and b/res/drawable-mdpi/ic_menu_save.png differ
diff --git a/res/drawable-xhdpi/ic_launcher.png b/res/drawable-xhdpi/ic_launcher.png
new file mode 100644
index 0000000..a9856e5
Binary files /dev/null and b/res/drawable-xhdpi/ic_launcher.png differ
diff --git a/res/drawable-xhdpi/ic_menu_save.png b/res/drawable-xhdpi/ic_menu_save.png
new file mode 100644
index 0000000..62a66d8
Binary files /dev/null and b/res/drawable-xhdpi/ic_menu_save.png differ
diff --git a/res/layout/app_list_item.xml b/res/layout/app_list_item.xml
new file mode 100644
index 0000000..da3131b
--- /dev/null
+++ b/res/layout/app_list_item.xml
@@ -0,0 +1,29 @@
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/res/layout/app_permission_item.xml b/res/layout/app_permission_item.xml
new file mode 100644
index 0000000..159f3c4
--- /dev/null
+++ b/res/layout/app_permission_item.xml
@@ -0,0 +1,29 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/res/layout/app_settings.xml b/res/layout/app_settings.xml
new file mode 100644
index 0000000..74b87d5
--- /dev/null
+++ b/res/layout/app_settings.xml
@@ -0,0 +1,128 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/res/layout/main.xml b/res/layout/main.xml
new file mode 100644
index 0000000..3181af6
--- /dev/null
+++ b/res/layout/main.xml
@@ -0,0 +1,90 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/res/menu/menu_app.xml b/res/menu/menu_app.xml
new file mode 100644
index 0000000..15867d4
--- /dev/null
+++ b/res/menu/menu_app.xml
@@ -0,0 +1,15 @@
+
+
\ No newline at end of file
diff --git a/res/values/strings.xml b/res/values/strings.xml
new file mode 100644
index 0000000..dd191d8
--- /dev/null
+++ b/res/values/strings.xml
@@ -0,0 +1,7 @@
+
+
+ Xposed App Settings
+ An Xposed module by
+ rovo89 and Tungstwenty.
+ You can use this thread for questions or suggestions.
+
\ No newline at end of file
diff --git a/src/de/robv/android/xposed/mods/appsettings/XposedMod.java b/src/de/robv/android/xposed/mods/appsettings/XposedMod.java
new file mode 100644
index 0000000..f363718
--- /dev/null
+++ b/src/de/robv/android/xposed/mods/appsettings/XposedMod.java
@@ -0,0 +1,261 @@
+package de.robv.android.xposed.mods.appsettings;
+
+
+import static de.robv.android.xposed.XposedHelpers.findAndHookMethod;
+import static de.robv.android.xposed.XposedHelpers.findClass;
+import static de.robv.android.xposed.XposedHelpers.getObjectField;
+import static de.robv.android.xposed.XposedHelpers.setFloatField;
+
+import java.util.Locale;
+
+import android.app.AndroidAppHelper;
+import android.content.Context;
+import android.content.IntentFilter;
+import android.content.SharedPreferences;
+import android.content.res.Configuration;
+import android.content.res.Resources;
+import android.content.res.XResources;
+import android.util.DisplayMetrics;
+import android.view.Display;
+import de.robv.android.xposed.IXposedHookCmdInit;
+import de.robv.android.xposed.IXposedHookLoadPackage;
+import de.robv.android.xposed.IXposedHookZygoteInit;
+import de.robv.android.xposed.XC_MethodHook;
+import de.robv.android.xposed.XposedBridge;
+import de.robv.android.xposed.callbacks.XC_LoadPackage.LoadPackageParam;
+import de.robv.android.xposed.mods.appsettings.hooks.PackagePermissions;
+
+public class XposedMod implements IXposedHookZygoteInit, IXposedHookLoadPackage, IXposedHookCmdInit {
+
+ public static final String MY_PACKAGE_NAME = XposedMod.class.getPackage().getName();
+ public static SharedPreferences prefs;
+
+ public static final String ACTION_PERMISSIONS = "update_permissions";
+
+ public static final String PREFS = "ModSettings";
+
+ public static final String PREF_ACTIVE = "/active";
+ public static final String PREF_DPI = "/dpi";
+ public static final String PREF_LOCALE = "/locale";
+ public static final String PREF_SCREEN = "/screen";
+ public static final String PREF_TABLET = "/tablet";
+ public static final String PREF_RESIDENT = "/resident";
+ public static final String PREF_REVOKEPERMS = "/revoke-perms";
+ public static final String PREF_REVOKELIST = "/revoke-list";
+
+
+ public static final String PREF_DEFAULT = "default";
+
+
+ private static int[] swdp = { 0, 320, 480, 600, 800, 1000 };
+ private static int[] wdp = { 0, 320, 480, 600, 800, 1000 };
+ private static int[] hdp = { 0, 480, 854, 1024, 1280, 1600 };
+ private static int[] w = { 0, 320, 480, 600, 800, 1000 };
+ private static int[] h = { 0, 480, 854, 1024, 1280, 1600 };
+ public static String[] screens = { "(default)", "320x480", "480x854", "600x1024", "800x1280", "1000x1600" };
+
+
+ @Override
+ public void initZygote(de.robv.android.xposed.IXposedHookZygoteInit.StartupParam startupParam) throws Throwable {
+
+ /*
+ * Do not load the preferences in the Zygote process (else they will be
+ * inherited) but rather on startup of each forked process.
+ */
+ try {
+ findAndHookMethod("com.android.internal.os.ZygoteInit", XposedMod.class.getClassLoader(), "handleSystemServerProcess", "com.android.internal.os.ZygoteConnection.Arguments", new XC_MethodHook() {
+ @Override
+ protected void beforeHookedMethod(MethodHookParam param) throws Throwable {
+ // Load the preferences object that can be used throughout
+ // the entire systemserver process
+ prefs = AndroidAppHelper.getSharedPreferencesForPackage(MY_PACKAGE_NAME, PREFS, Context.MODE_PRIVATE);
+
+ // Other actions done at the very beginning of systemserver
+ // may go here
+ }
+ });
+ } catch (Throwable t) {
+ XposedBridge.log(t);
+ }
+
+ try {
+ findAndHookMethod("com.android.internal.os.ZygoteConnection", XposedMod.class.getClassLoader(), "handleChildProc",
+ "com.android.internal.os.ZygoteConnection.Arguments", "[Ljava.io.FileDescriptor;",
+ "java.io.FileDescriptor", "java.io.PrintStream", new XC_MethodHook() {
+ @Override
+ protected void beforeHookedMethod(MethodHookParam param) throws Throwable {
+ // Load the preferences object that can be used throughout
+ // the entire process
+ prefs = AndroidAppHelper.getSharedPreferencesForPackage(MY_PACKAGE_NAME, PREFS, Context.MODE_PRIVATE);
+
+ // Other actions done at the very beginning of other
+ // processes forked by Zygote may go here
+ }
+ });
+ } catch (Throwable t) {
+ XposedBridge.log(t);
+ }
+
+
+
+ // Hook to override DPI (globally, including resource load + rendering)
+ try {
+ findAndHookMethod(Display.class, "init", int.class, new XC_MethodHook() {
+ @Override
+ protected void afterHookedMethod(MethodHookParam param) throws Throwable {
+ String packageName = AndroidAppHelper.currentPackageName();
+
+ if (!prefs.getBoolean(packageName + PREF_ACTIVE, false)) {
+ // No overrides for this package
+ return;
+ }
+
+ int packageDPI = prefs.getInt(packageName + PREF_DPI,
+ prefs.getInt(PREF_DEFAULT + PREF_DPI, 0));
+ if (packageDPI > 0) {
+ // Density for this package is overridden, change density
+ setFloatField(param.thisObject, "mDensity", packageDPI / 160.0f);
+ }
+ };
+ });
+ } catch (Throwable t) {
+ XposedBridge.log(t);
+ }
+
+ // Override settings used when loading resources
+ try {
+ findAndHookMethod(Resources.class, "updateConfiguration",
+ Configuration.class, DisplayMetrics.class, "android.content.res.CompatibilityInfo",
+ new XC_MethodHook() {
+
+ @Override
+ protected void beforeHookedMethod(MethodHookParam param) throws Throwable {
+ if (param.args[0] != null && param.thisObject instanceof XResources) {
+ String packageName = ((XResources) param.thisObject).getPackageName();
+ if (packageName != null) {
+
+ int screen = prefs.getInt(packageName + PREF_SCREEN,
+ prefs.getInt(PREF_DEFAULT + PREF_SCREEN, 0));
+ if (screen < 0 || screen >= swdp.length)
+ screen = 0;
+
+ int swdp = XposedMod.swdp[screen];
+ int wdp = XposedMod.wdp[screen];
+ int hdp = XposedMod.hdp[screen];
+ int w = XposedMod.w[screen];
+ int h = XposedMod.h[screen];
+
+ boolean tablet = prefs.getBoolean(packageName + PREF_TABLET, false);
+
+ Locale loc = getPackageSpecificLocale(packageName);
+
+ if (swdp > 0 || loc != null || tablet) {
+ Configuration newConfig = new Configuration((Configuration) param.args[0]);
+ if (swdp > 0) {
+ newConfig.smallestScreenWidthDp = swdp;
+ newConfig.screenWidthDp = wdp;
+ newConfig.screenHeightDp = hdp;
+ }
+ if (loc != null) {
+ newConfig.locale = loc;
+ // Also set the locale as the app-wide default,
+ // for purposes other than resource loading
+ if (AndroidAppHelper.currentPackageName().equals(packageName))
+ Locale.setDefault(loc);
+ }
+ if (tablet)
+ newConfig.screenLayout |= Configuration.SCREENLAYOUT_SIZE_XLARGE;
+ param.args[0] = newConfig;
+
+ if (w > 0 && param.args[1] != null) {
+ DisplayMetrics newMetrics = new DisplayMetrics();
+ newMetrics.setTo((DisplayMetrics) param.args[1]);
+ newMetrics.widthPixels = w;
+ newMetrics.heightPixels = h;
+ param.args[1] = newMetrics;
+ }
+ }
+ }
+ }
+ }
+ });
+ } catch (Throwable t) {
+ XposedBridge.log(t);
+ }
+
+
+ /* Hook to the PackageManager service in order to
+ * - Listen for broadcasts to apply new settings
+ * - Intercept package installations and apply existing settings
+ */
+ try {
+ final Class> clsPMS = findClass("com.android.server.pm.PackageManagerService", XposedMod.class.getClassLoader());
+
+ // Listen for broadcasts from the Settings part of the mod, so it's applied immediately
+ findAndHookMethod(clsPMS, "systemReady", new XC_MethodHook() {
+ @Override
+ protected void afterHookedMethod(MethodHookParam param)
+ throws Throwable {
+ Context mContext = (Context) getObjectField(param.thisObject, "mContext");
+ mContext.registerReceiver(new PackagePermissions(param.thisObject),
+ new IntentFilter(MY_PACKAGE_NAME + ".UPDATE_PERMISSIONS"),
+ MY_PACKAGE_NAME + ".BROADCAST_PERMISSION",
+ null);
+ }
+ });
+
+ // Re-apply revoked permissions after a certain package is reinstalled or updated
+ findAndHookMethod(clsPMS, "installPackageLI", "com.android.server.pm.PackageManagerService$InstallArgs",
+ boolean.class, "com.android.server.pm.PackageManagerService$PackageInstalledInfo",
+ new XC_MethodHook() {
+ @Override
+ protected void afterHookedMethod(MethodHookParam param)
+ throws Throwable {
+ String pkgName = (String) getObjectField(param.args[2], "name");
+ if (pkgName == null) {
+ return;
+ }
+ try {
+ PackagePermissions.revokePermissions(param.thisObject, pkgName, false, false);
+ } catch (Throwable t) {
+ XposedBridge.log(t);
+ }
+ }
+ });
+ } catch (Throwable e) {
+ XposedBridge.log(e);
+ }
+ }
+
+
+ @Override
+ public void handleLoadPackage(LoadPackageParam lpparam) throws Throwable {
+
+ // Override the default Locale if one is defined (not res-related, here)
+ if (prefs.getBoolean(lpparam.packageName + PREF_ACTIVE, false)) {
+ Locale packageLocale = getPackageSpecificLocale(lpparam.packageName);
+ if (packageLocale != null)
+ Locale.setDefault(packageLocale);
+ }
+ }
+
+
+ private static Locale getPackageSpecificLocale(String packageName) {
+ String locale = prefs.getString(packageName + PREF_LOCALE, null);
+ if (locale == null || locale.isEmpty())
+ return null;
+
+ String[] localeParts = locale.split("_", 3);
+ String language = localeParts[0];
+ String region = (localeParts.length >= 2) ? localeParts[1] : "";
+ String variant = (localeParts.length >= 3) ? localeParts[2] : "";
+ return new Locale(language, region, variant);
+ }
+
+
+ @Override
+ public void initCmdApp(de.robv.android.xposed.IXposedHookCmdInit.StartupParam startupParam) throws Throwable {
+ prefs = AndroidAppHelper.getSharedPreferencesForPackage(MY_PACKAGE_NAME, PREFS, Context.MODE_PRIVATE);
+ }
+
+}
diff --git a/src/de/robv/android/xposed/mods/appsettings/XposedModActivity.java b/src/de/robv/android/xposed/mods/appsettings/XposedModActivity.java
new file mode 100644
index 0000000..2797d4b
--- /dev/null
+++ b/src/de/robv/android/xposed/mods/appsettings/XposedModActivity.java
@@ -0,0 +1,402 @@
+package de.robv.android.xposed.mods.appsettings;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.regex.Pattern;
+
+import android.annotation.SuppressLint;
+import android.app.Activity;
+import android.app.ProgressDialog;
+import android.content.Context;
+import android.content.Intent;
+import android.content.SharedPreferences;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageManager;
+import android.content.pm.PackageManager.NameNotFoundException;
+import android.graphics.Color;
+import android.os.AsyncTask;
+import android.os.Bundle;
+import android.text.method.LinkMovementMethod;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.AdapterView;
+import android.widget.ArrayAdapter;
+import android.widget.CheckBox;
+import android.widget.CompoundButton;
+import android.widget.Filter;
+import android.widget.ImageView;
+import android.widget.ListView;
+import android.widget.SearchView;
+import android.widget.SectionIndexer;
+import android.widget.TabHost;
+import android.widget.TabHost.TabSpec;
+import android.widget.TextView;
+import de.robv.android.xposed.mods.appsettings.settings.ApplicationSettings;
+
+
+public class XposedModActivity extends Activity {
+
+ private ArrayList appList = new ArrayList();
+ private ArrayList filteredAppList = new ArrayList();
+ private String activeFilter;
+ private SharedPreferences prefs;
+
+
+ @SuppressLint("WorldReadableFiles")
+ @Override
+ public void onCreate(Bundle savedInstanceState) {
+ setTitle(R.string.app_name);
+ super.onCreate(savedInstanceState);
+
+ prefs = getSharedPreferences(XposedMod.PREFS, Context.MODE_WORLD_READABLE);
+
+ setContentView(R.layout.main);
+
+ TabHost tabHost=(TabHost)findViewById(R.id.tabHost);
+ tabHost.setup();
+
+ TabSpec spec1=tabHost.newTabSpec("App Settings");
+ spec1.setIndicator("App Settings");
+ spec1.setContent(R.id.tab1);
+
+ TabSpec spec2=tabHost.newTabSpec("About");
+ spec2.setIndicator("About");
+ spec2.setContent(R.id.tab2);
+
+ tabHost.addTab(spec1);
+ tabHost.addTab(spec2);
+ tabHost.setCurrentTab(0);
+
+ ((TextView) findViewById(R.id.about_title)).setMovementMethod(LinkMovementMethod.getInstance());
+
+ try {
+ ((TextView) findViewById(R.id.version)).setText("Version: " +
+ getPackageManager().getPackageInfo(getPackageName(), 0).versionName);
+ } catch (NameNotFoundException e) {
+ }
+
+
+ ListView list = (ListView) findViewById(R.id.lstApps);
+
+ list.setOnItemClickListener(new AdapterView.OnItemClickListener() {
+
+ @Override
+ public void onItemClick(AdapterView> parent, View view, int position, long id) {
+ // Open settings activity when clicking on an application
+ String pkgName = ((TextView) view.findViewById(R.id.app_package)).getText().toString();
+ Intent i = new Intent(getApplicationContext(), ApplicationSettings.class);
+ i.putExtra("package", pkgName);
+ startActivityForResult(i, position);
+ }
+ });
+
+ // Load the list of apps in the background
+ new PrepareAppsAdapter().execute();
+ }
+
+
+ @Override
+ protected void onActivityResult(int requestCode, int resultCode, Intent data) {
+ super.onActivityResult(requestCode, resultCode, data);
+
+ // Refresh the app that was just edited, if it's visible in the list
+ ListView list = (ListView) findViewById(R.id.lstApps);
+ if (requestCode >= list.getFirstVisiblePosition() &&
+ requestCode <= list.getLastVisiblePosition()) {
+ View v = list.getChildAt(requestCode - list.getFirstVisiblePosition());
+ list.getAdapter().getView(requestCode, v, list);
+ }
+ }
+
+
+
+ @SuppressLint("DefaultLocale")
+ private void loadApps() {
+
+ appList.clear();
+
+ PackageManager pm = getPackageManager();
+ List apps = getPackageManager().getInstalledApplications(0);
+ for (ApplicationInfo appInfo : apps) {
+ appInfo.name = appInfo.loadLabel(pm).toString();
+ appList.add(appInfo);
+ }
+
+ Collections.sort(appList, new Comparator() {
+ @Override
+ public int compare(ApplicationInfo lhs, ApplicationInfo rhs) {
+ if (lhs.name == null) {
+ return -1;
+ } else if (rhs.name == null) {
+ return 1;
+ } else {
+ return lhs.name.toUpperCase().compareTo(rhs.name.toUpperCase());
+ }
+ }
+ });
+ }
+
+
+ private void prepareAppList() {
+ final AppListAdaptor appListAdaptor = new AppListAdaptor(XposedModActivity.this,appList);
+
+ ((ListView) findViewById(R.id.lstApps)).setAdapter(appListAdaptor);
+ ((SearchView) findViewById(R.id.searchApp)).setOnQueryTextListener(new SearchView.OnQueryTextListener() {
+
+ @Override
+ public boolean onQueryTextSubmit(String query) {
+ activeFilter = query;
+ appListAdaptor.getFilter().filter(activeFilter);
+ ((SearchView) findViewById(R.id.searchApp)).clearFocus();
+ return false;
+ }
+
+ @Override
+ public boolean onQueryTextChange(String newText) {
+ activeFilter = newText;
+ appListAdaptor.getFilter().filter(activeFilter);
+ return false;
+ }
+
+
+ });
+
+ ((CheckBox) findViewById(R.id.chkOnlyTweaked)).setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
+
+ @Override
+ public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
+ appListAdaptor.getFilter().filter(activeFilter);
+ }
+ });
+ }
+
+
+ // Handle background loading of apps
+ private class PrepareAppsAdapter extends AsyncTask {
+ ProgressDialog dialog;
+
+ @Override
+ protected void onPreExecute() {
+ dialog = new ProgressDialog(((ListView) findViewById(R.id.lstApps)).getContext());
+ dialog.setMessage("Loading apps, please wait");
+ dialog.setIndeterminate(true);
+ dialog.setCancelable(false);
+ dialog.show();
+ }
+
+ @Override
+ protected AppListAdaptor doInBackground(Void... params) {
+ if (appList.size() == 0) {
+ loadApps();
+ }
+ return null;
+ }
+
+ @Override
+ protected void onPostExecute(final AppListAdaptor result) {
+ prepareAppList();
+
+ try {
+ dialog.dismiss();
+ } catch (Exception e) {
+
+ }
+ }
+ }
+
+
+ private class AppListFilter extends Filter {
+
+ private AppListAdaptor adaptor;
+
+ AppListFilter(AppListAdaptor adaptor) {
+ super();
+ this.adaptor = adaptor;
+ }
+
+ @SuppressLint("WorldReadableFiles")
+ @Override
+ protected FilterResults performFiltering(CharSequence constraint) {
+ // NOTE: this function is *always* called from a background thread, and
+ // not the UI thread.
+
+ ArrayList items = new ArrayList();
+ synchronized (this) {
+ items.addAll(appList);
+ }
+
+ boolean onlyTweaked = ((CheckBox) findViewById(R.id.chkOnlyTweaked)).isChecked();
+ SharedPreferences prefs = getSharedPreferences(XposedMod.PREFS, Context.MODE_WORLD_READABLE);
+
+ FilterResults result = new FilterResults();
+ if (constraint != null && constraint.length() > 0) {
+ Pattern regexp = Pattern.compile(constraint.toString(), Pattern.LITERAL | Pattern.CASE_INSENSITIVE);
+ for (Iterator i = items.iterator(); i.hasNext(); ) {
+ ApplicationInfo app = i.next();
+ if (!regexp.matcher(app.name).find() && !regexp.matcher(app.packageName).find()) {
+ i.remove();
+ }
+ }
+ }
+ for (Iterator i = items.iterator(); i.hasNext(); ) {
+ ApplicationInfo app = i.next();
+ if (onlyTweaked && !prefs.getBoolean(app.packageName + XposedMod.PREF_ACTIVE, false)) {
+ i.remove();
+ }
+ }
+
+ result.values = items;
+ result.count = items.size();
+
+ return result;
+ }
+
+
+ @SuppressWarnings("unchecked")
+ @Override
+ protected void publishResults(CharSequence constraint, FilterResults results) {
+ // NOTE: this function is *always* called from the UI thread.
+ filteredAppList = (ArrayList) results.values;
+ adaptor.notifyDataSetChanged();
+ adaptor.clear();
+ for(int i = 0, l = filteredAppList.size(); i < l; i++) {
+ adaptor.add(filteredAppList.get(i));
+ }
+ adaptor.notifyDataSetInvalidated();
+ }
+ }
+
+
+
+ class AppListAdaptor extends ArrayAdapter implements SectionIndexer {
+
+ private Map alphaIndexer;
+ private String[] sections;
+ private Filter filter;
+
+
+ @SuppressLint("DefaultLocale")
+ public AppListAdaptor(Context context, List items) {
+ super(context, R.layout.app_list_item, new ArrayList(items));
+
+ filteredAppList.addAll(items);
+
+ filter = new AppListFilter(this);
+
+ alphaIndexer = new HashMap();
+ for(int i = filteredAppList.size() - 1; i >= 0; i--)
+ {
+ ApplicationInfo app = filteredAppList.get(i);
+ String firstChar = app.name.substring(0, 1).toUpperCase();
+ if(firstChar.charAt(0) > 'Z' || firstChar.charAt(0) < 'A')
+ firstChar = "@";
+
+ alphaIndexer.put(firstChar, i);
+ }
+
+ Set sectionLetters = alphaIndexer.keySet();
+
+ // create a list from the set to sort
+ List sectionList = new ArrayList(sectionLetters);
+
+ Collections.sort(sectionList);
+
+ sections = new String[sectionList.size()];
+
+ sectionList.toArray(sections);
+ }
+
+ @Override
+ public View getView(int position, View convertView, ViewGroup parent) {
+ // Load or reuse the view for this row
+ View row = convertView;
+ if (row == null) {
+ row = getLayoutInflater().inflate(R.layout.app_list_item, parent, false);
+ }
+
+ ApplicationInfo app = filteredAppList.get(position);
+
+ ((TextView) row.findViewById(R.id.app_name)).setText(app.name);
+ ((TextView) row.findViewById(R.id.app_package)).setTextColor(prefs.getBoolean(app.packageName + XposedMod.PREF_ACTIVE,
+ false) ? Color.RED : Color.parseColor("#0099CC"));
+ ((TextView) row.findViewById(R.id.app_package)).setText(app.packageName);
+ ((ImageView) row.findViewById(R.id.app_icon)).setImageDrawable(app.loadIcon(getPackageManager()));
+
+ return row;
+ }
+
+ @SuppressLint("DefaultLocale")
+ @Override
+ public void notifyDataSetInvalidated() {
+ alphaIndexer.clear();
+ for (int i = filteredAppList.size() - 1; i >= 0; i--) {
+ ApplicationInfo app = filteredAppList.get(i);
+ String firstChar = app.name.substring(0, 1).toUpperCase();
+ if (firstChar.charAt(0) > 'Z' || firstChar.charAt(0) < 'A')
+ firstChar = "@";
+ alphaIndexer.put(firstChar, i);
+ }
+
+ Set keys = alphaIndexer.keySet();
+ Iterator it = keys.iterator();
+ ArrayList keyList = new ArrayList();
+ while (it.hasNext()) {
+ keyList.add(it.next());
+ }
+
+ Collections.sort(keyList);
+ sections = new String[keyList.size()];
+ keyList.toArray(sections);
+
+ super.notifyDataSetInvalidated();
+ }
+
+ @Override
+ public int getPositionForSection(int section) {
+ return alphaIndexer.get(sections[section]);
+ }
+
+ @Override
+ public int getSectionForPosition(int position) {
+
+ // Iterate over the sections to find the closest index
+ // that is not greater than the position
+ int closestIndex = 0;
+ int latestDelta = Integer.MAX_VALUE;
+
+ for (int i = 0; i < sections.length; i++) {
+ int current = alphaIndexer.get(sections[i]);
+ if (current == position) {
+ // If position matches an index, return it immediately
+ return i;
+ } else if (current < position) {
+ // Check if this is closer than the last index we inspected
+ int delta = position - current;
+ if (delta < latestDelta) {
+ closestIndex = i;
+ latestDelta = delta;
+ }
+ }
+ }
+
+ return closestIndex;
+ }
+
+ @Override
+ public Object[] getSections() {
+ return sections;
+ }
+
+ @Override
+ public Filter getFilter() {
+ return filter;
+ }
+ }
+
+}
diff --git a/src/de/robv/android/xposed/mods/appsettings/hooks/PackagePermissions.java b/src/de/robv/android/xposed/mods/appsettings/hooks/PackagePermissions.java
new file mode 100644
index 0000000..a6a5302
--- /dev/null
+++ b/src/de/robv/android/xposed/mods/appsettings/hooks/PackagePermissions.java
@@ -0,0 +1,172 @@
+package de.robv.android.xposed.mods.appsettings.hooks;
+
+import static de.robv.android.xposed.XposedHelpers.findClass;
+import static de.robv.android.xposed.XposedHelpers.findMethodBestMatch;
+import static de.robv.android.xposed.XposedHelpers.findMethodExact;
+import static de.robv.android.xposed.XposedHelpers.getObjectField;
+
+import java.lang.reflect.InvocationTargetException;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+import android.app.AndroidAppHelper;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.ApplicationInfo;
+import android.os.Build;
+import de.robv.android.xposed.XposedBridge;
+import de.robv.android.xposed.mods.appsettings.XposedMod;
+
+
+
+public class PackagePermissions extends BroadcastReceiver {
+
+ public static final String REVOKED_PREFIX = "stericson.disabled.";
+
+ private Object pmSvc;
+
+ public PackagePermissions(Object pmSvc) {
+ this.pmSvc = pmSvc;
+ }
+
+
+ @Override
+ public void onReceive(Context context, Intent intent) {
+
+ try {
+ // The app broadcasted a request to update settings for a running app
+
+ // Validate the action being requested
+ if (!XposedMod.ACTION_PERMISSIONS.equals(intent.getExtras().getString("action")))
+ return;
+
+ String pkgName = intent.getExtras().getString("Package");
+ boolean killApp = intent.getExtras().getBoolean("Kill", false);
+
+ // Update the PM's configuration for this package
+ revokePermissions(pmSvc, pkgName, killApp, true);
+
+ } catch (Throwable t) {
+ XposedBridge.log(t);
+ }
+ }
+
+
+ @SuppressWarnings("unchecked")
+ public static void revokePermissions(Object pmSvc, String pkgName, boolean killApp, boolean force) throws Throwable {
+ // Reload preferences if they were updated before the broadcast
+ if (force)
+ XposedMod.prefs = AndroidAppHelper.getSharedPreferencesForPackage(XposedMod.MY_PACKAGE_NAME, XposedMod.PREFS, Context.MODE_PRIVATE);
+
+ Map mPackages = (Map) getObjectField(pmSvc, "mPackages");
+ synchronized (mPackages) {
+ Object pkgInfo = mPackages.get(pkgName);
+
+ // Apply new permissions if needed
+ if (XposedMod.prefs.getBoolean(pkgName + XposedMod.PREF_ACTIVE, false) || force)
+ doRevokePermissions(pmSvc, pkgName, mPackages, pkgInfo);
+
+ if (killApp) {
+ try {
+ ApplicationInfo appInfo = (ApplicationInfo) getObjectField(pkgInfo, "applicationInfo");
+ findMethodExact(pmSvc.getClass(), "killApplication", String.class, int.class).invoke(
+ pmSvc, pkgName, appInfo.uid);
+ } catch (Throwable t) {
+ XposedBridge.log(t);
+ }
+ }
+ }
+ }
+
+
+ @SuppressWarnings("unchecked")
+ private static void doRevokePermissions(Object pmSvc, String pkgName, Map mPackages1, Object pkgInfo)
+ throws IllegalAccessException, InvocationTargetException {
+ Object mSettings = getObjectField(pmSvc, "mSettings");
+ Map mPackages2 = (Map) getObjectField(mSettings, "mPackages");
+ Object pkgSettings = mPackages2.get(pkgName);
+
+ Set grantedPermissions = (Set) getObjectField(pkgSettings, "grantedPermissions");
+
+ Set disabledPermissions;
+ if (XposedMod.prefs.getBoolean(pkgName + XposedMod.PREF_REVOKEPERMS, false))
+ disabledPermissions = XposedMod.prefs.getStringSet(pkgName + XposedMod.PREF_REVOKELIST, new HashSet());
+ else
+ disabledPermissions = new HashSet();
+
+ setDisabledPermissions(grantedPermissions, disabledPermissions);
+
+ // Save updated information to the packages.xml file
+ try {
+ findMethodExact(mSettings.getClass(), "writeLPr").invoke(mSettings);
+ } catch (Throwable t) {
+ XposedBridge.log(t);
+ }
+
+
+ Set grantedPermissions1 = (Set) getObjectField(
+ getObjectField(mPackages1.get(pkgName), "mExtras"), "grantedPermissions");
+ setDisabledPermissions(grantedPermissions1, disabledPermissions);
+
+ List requestedPermissions;
+ requestedPermissions = (List) getObjectField(mPackages1.get(pkgName), "requestedPermissions");
+ Set requestedPermissionsSet = new HashSet(requestedPermissions);
+ setDisabledPermissions(requestedPermissionsSet, disabledPermissions);
+ requestedPermissions.clear();
+ requestedPermissions.addAll(requestedPermissionsSet);
+
+
+ // Also update the permissions associated with the user, in case they're not the same object as the package's
+ Integer userId = null;
+ if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.ICE_CREAM_SANDWICH_MR1) {
+ // ICS
+ userId = (Integer) getObjectField(pkgSettings, "userId");
+ } else {
+ // TODO Check if this works properly on JB
+ userId = (Integer) getObjectField(pkgSettings, "appId");
+ Class> clsUserId = findClass("android.os.UserId", pmSvc.getClass().getClassLoader());
+ int userId2 = (Integer) findMethodExact(clsUserId, "getUserId", int.class).invoke(clsUserId, userId);
+ if (userId2 != 0) {
+ userId = userId2;
+ }
+ }
+
+ Object userPerms = findMethodExact("com.android.server.pm.Settings", pmSvc.getClass().getClassLoader(),
+ "getUserIdLPr", int.class).invoke(mSettings, userId);
+ Set uidPermissions = (Set) getObjectField(userPerms, "grantedPermissions");
+ setDisabledPermissions(uidPermissions, disabledPermissions);
+
+ try {
+ findMethodBestMatch(pmSvc.getClass(), "grantPermissionsLPw", pkgInfo.getClass(), boolean.class).invoke(
+ pmSvc, pkgInfo, true);
+ } catch (Throwable t) {
+ XposedBridge.log(t);
+ }
+ }
+
+
+ private static void setDisabledPermissions(Set permissions, Set disabled) {
+ Set reEnablePerms = new HashSet();
+ for (String perm : permissions) {
+ if (perm.startsWith(REVOKED_PREFIX)) {
+ reEnablePerms.add(perm.substring(REVOKED_PREFIX.length()));
+ }
+ }
+ for (String perm : reEnablePerms) {
+ permissions.add(perm);
+ permissions.remove(REVOKED_PREFIX + perm);
+ }
+ for (String perm : disabled) {
+ if (permissions.contains(perm)) {
+ permissions.remove(perm);
+ } else {
+ // Adding disabled permission not previously on the list !
+ }
+ permissions.add(REVOKED_PREFIX + perm);
+ }
+ }
+
+}
diff --git a/src/de/robv/android/xposed/mods/appsettings/settings/ApplicationSettings.java b/src/de/robv/android/xposed/mods/appsettings/settings/ApplicationSettings.java
new file mode 100644
index 0000000..3fc6ab5
--- /dev/null
+++ b/src/de/robv/android/xposed/mods/appsettings/settings/ApplicationSettings.java
@@ -0,0 +1,683 @@
+package de.robv.android.xposed.mods.appsettings.settings;
+
+import java.text.Collator;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.Enumeration;
+import java.util.HashSet;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Locale;
+import java.util.Set;
+import java.util.TreeSet;
+import java.util.jar.JarEntry;
+import java.util.jar.JarFile;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+import android.annotation.SuppressLint;
+import android.app.Activity;
+import android.app.AlertDialog;
+import android.content.Context;
+import android.content.DialogInterface;
+import android.content.Intent;
+import android.content.SharedPreferences;
+import android.content.SharedPreferences.Editor;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageInfo;
+import android.content.pm.PackageManager;
+import android.content.pm.PackageManager.NameNotFoundException;
+import android.content.pm.PermissionInfo;
+import android.content.res.Resources;
+import android.graphics.Color;
+import android.graphics.Paint;
+import android.graphics.PorterDuff.Mode;
+import android.graphics.drawable.Drawable;
+import android.net.Uri;
+import android.os.Bundle;
+import android.text.Editable;
+import android.text.TextWatcher;
+import android.view.Menu;
+import android.view.MenuItem;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.AdapterView;
+import android.widget.ArrayAdapter;
+import android.widget.Button;
+import android.widget.CheckBox;
+import android.widget.CompoundButton;
+import android.widget.EditText;
+import android.widget.ImageView;
+import android.widget.ListView;
+import android.widget.ScrollView;
+import android.widget.Spinner;
+import android.widget.Switch;
+import android.widget.TextView;
+import de.robv.android.xposed.mods.appsettings.R;
+import de.robv.android.xposed.mods.appsettings.XposedMod;
+import de.robv.android.xposed.mods.appsettings.hooks.PackagePermissions;
+
+@SuppressLint("WorldReadableFiles")
+public class ApplicationSettings extends Activity {
+
+ private boolean dirty = false;
+
+
+ private String pkgName;
+ SharedPreferences prefs;
+ private Set disabledPermissions = new HashSet();
+ private boolean allowRevoking;
+ private Intent parentIntent;
+
+ private List permsList = new LinkedList();
+
+ String[] localeCodes;
+ String[] localeDescriptions;
+ int selectedLocale;
+
+
+ /** Called when the activity is first created. */
+ @Override
+ public void onCreate(Bundle savedInstanceState) {
+
+ super.onCreate(savedInstanceState);
+
+ setContentView(R.layout.app_settings);
+
+ Intent i = getIntent();
+ parentIntent = i;
+
+ prefs = getSharedPreferences(XposedMod.PREFS, Context.MODE_WORLD_READABLE);
+
+ ApplicationInfo app;
+ try {
+ app = getPackageManager().getApplicationInfo(i.getStringExtra("package"), 0);
+ pkgName = app.packageName;
+ } catch (NameNotFoundException e) {
+ throw new RuntimeException("Invalid package: " + i.getStringExtra("package"));
+ }
+
+ // Display app info
+ ((TextView) findViewById(R.id.app_label)).setText(app.loadLabel(getPackageManager()));
+ ((TextView) findViewById(R.id.package_name)).setText(app.packageName);
+ ((ImageView) findViewById(R.id.app_icon)).setImageDrawable(app.loadIcon(getPackageManager()));
+
+ // Update switch of active/inactive tweaks
+ if (prefs.getBoolean(pkgName + XposedMod.PREF_ACTIVE, false)) {
+ ((Switch) findViewById(R.id.switchAppTweaked)).setChecked(true);
+ findViewById(R.id.viewTweaks).setVisibility(View.VISIBLE);
+ } else {
+ ((Switch) findViewById(R.id.switchAppTweaked)).setChecked(false);
+ findViewById(R.id.viewTweaks).setVisibility(View.GONE);
+ }
+ // Toggle the visibility of the lower panel when changed
+ ((Switch) findViewById(R.id.switchAppTweaked)).setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
+ @Override
+ public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
+ dirty = true;
+ findViewById(R.id.viewTweaks).setVisibility(isChecked ? View.VISIBLE : View.GONE);
+ }
+ });
+
+ // Update DPI field
+ if (prefs.getBoolean(pkgName + XposedMod.PREF_ACTIVE, false)) {
+ ((EditText) findViewById(R.id.txtDPI)).setText(String.valueOf(
+ prefs.getInt(pkgName + XposedMod.PREF_DPI, 0)));
+ } else {
+ ((EditText) findViewById(R.id.txtDPI)).setText("0");
+ }
+ // Track changes to the DPI field to know if the settings were changed
+ ((EditText) findViewById(R.id.txtDPI)).addTextChangedListener(new TextWatcher() {
+ @Override
+ public void onTextChanged(CharSequence s, int start, int before, int count) {
+ dirty = true;
+ }
+ @Override
+ public void beforeTextChanged(CharSequence s, int start, int count, int after) {
+ }
+ @Override
+ public void afterTextChanged(Editable s) {
+ }
+ });
+
+ // Load and render current screen setting + possible options
+ int screen = prefs.getInt(pkgName + XposedMod.PREF_SCREEN, 0);
+ if (screen < 0 || screen >= XposedMod.screens.length)
+ screen = 0;
+ final int selectedScreen = screen;
+
+ Spinner spnScreen = (Spinner) findViewById(R.id.spnScreen);
+ List lstScreens = Arrays.asList(XposedMod.screens);
+ ArrayAdapter screenAdapter = new ArrayAdapter(this,
+ android.R.layout.simple_spinner_item, lstScreens);
+ screenAdapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
+ spnScreen.setAdapter(screenAdapter);
+ spnScreen.setSelection(selectedScreen);
+ // Track changes to the screen to know if the settings were changed
+ spnScreen.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() {
+ @Override
+ public void onItemSelected(AdapterView> arg0, View arg1, int pos, long arg3) {
+ if (pos != selectedScreen) {
+ dirty = true;
+ }
+ }
+ @Override
+ public void onNothingSelected(AdapterView> arg0) {
+ }
+ });
+
+
+ // Update Tablet field
+ ((CheckBox) findViewById(R.id.chkTablet)).setChecked(prefs.getBoolean(pkgName + XposedMod.PREF_TABLET, false));
+ // Track changes to the Tablet checkbox to know if the settings were changed
+ ((CheckBox) findViewById(R.id.chkTablet)).setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
+ @Override
+ public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
+ dirty = true;
+ }
+ });
+
+
+ // Update Language and list of possibilities
+ prepareLocalesList();
+
+ Spinner spnLanguage = (Spinner) findViewById(R.id.spnLanguage);
+ List lstLanguages = Arrays.asList(localeDescriptions);
+ ArrayAdapter dataAdapter = new ArrayAdapter(this,
+ android.R.layout.simple_spinner_item, lstLanguages);
+ dataAdapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
+ spnLanguage.setAdapter(dataAdapter);
+ spnLanguage.setSelection(selectedLocale);
+ // Track changes to the language to know if the settings were changed
+ spnLanguage.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() {
+ @Override
+ public void onItemSelected(AdapterView> arg0, View arg1, int pos, long arg3) {
+ if (pos != selectedLocale) {
+ dirty = true;
+ }
+ }
+ @Override
+ public void onNothingSelected(AdapterView> arg0) {
+ }
+ });
+
+
+ // Helper to list all apk folders under /res
+ ((Button) findViewById(R.id.btnListRes)).setOnClickListener(new View.OnClickListener() {
+
+ @Override
+ public void onClick(View v) {
+ AlertDialog.Builder builder = new AlertDialog.Builder(ApplicationSettings.this);
+
+ ScrollView scrollPane = new ScrollView(ApplicationSettings.this);
+ TextView txtPane = new TextView(ApplicationSettings.this);
+ StringBuilder contents = new StringBuilder();
+ JarFile jar = null;
+ TreeSet resEntries = new TreeSet();
+ Matcher m = Pattern.compile("res/(.+)/[^/]+").matcher("");
+ try {
+ ApplicationInfo app = getPackageManager().getApplicationInfo(pkgName, 0);
+ jar = new JarFile(app.sourceDir);
+ Enumeration entries = jar.entries();
+ while (entries.hasMoreElements()) {
+ JarEntry entry = entries.nextElement();
+ m.reset(entry.getName());
+ if (m.matches())
+ resEntries.add(m.group(1));
+ }
+ if (resEntries.size() == 0)
+ resEntries.add("No resources found");
+ jar.close();
+ for (String dir : resEntries) {
+ contents.append('\n');
+ contents.append(dir);
+ }
+ contents.deleteCharAt(0);
+ } catch (Exception e) {
+ contents.append("Failed to load APK contents");
+ if (jar != null) {
+ try {
+ jar.close();
+ } catch (Exception ex) { }
+ }
+ }
+ txtPane.setText(contents);
+ scrollPane.addView(txtPane);
+ builder.setView(scrollPane);
+ builder.setTitle("Resources");
+ builder.show();
+ }
+ });
+
+
+ // Setting for permissions revoking
+ allowRevoking = prefs.getBoolean(pkgName + XposedMod.PREF_REVOKEPERMS, false);
+ ((CheckBox) findViewById(R.id.chkRevokePerms)).setChecked(allowRevoking);
+ // Track changes to the Revoke checkbox to know if the settings were changed
+ // and to lock or unlock the list of permissions
+ ((CheckBox) findViewById(R.id.chkRevokePerms)).setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
+ @Override
+ public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
+ dirty = true;
+ allowRevoking = isChecked;
+ findViewById(R.id.lstPermissions).setBackgroundColor(allowRevoking ? Color.BLACK : Color.DKGRAY);
+ }
+ });
+ findViewById(R.id.lstPermissions).setBackgroundColor(allowRevoking ? Color.BLACK : Color.DKGRAY);
+
+
+ // Setting for making the app resident in memory
+ ((CheckBox) findViewById(R.id.chkResident)).setChecked(prefs.getBoolean(pkgName + XposedMod.PREF_RESIDENT, false));
+ // Track changes to know if the settings were changed
+ ((CheckBox) findViewById(R.id.chkResident)).setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
+ @Override
+ public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
+ dirty = true;
+ }
+ });
+
+
+ // Load the list of permissions for the package and present them
+ try {
+ loadPermissionsList();
+ } catch (NameNotFoundException e) {
+ throw new RuntimeException("Invalid package permissions: " + pkgName, e);
+ }
+
+ final PermsListAdaptor appListAdaptor = new PermsListAdaptor(this, permsList);
+ ((ListView) findViewById(R.id.lstPermissions)).setAdapter(appListAdaptor);
+ }
+
+
+ @Override
+ public void onBackPressed() {
+ // If form wasn't changed, exit without prompting
+ if (!dirty) {
+ finish();
+ return;
+ }
+
+ // Require confirmation to exit the screen and lose configuration changes
+ AlertDialog.Builder builder = new AlertDialog.Builder(this);
+ builder.setTitle("Warning");
+ builder.setIconAttribute(android.R.attr.alertDialogIcon);
+ builder.setMessage("You didn't save the configuration. " +
+ "Really go back and discard changes?");
+ builder.setPositiveButton("Yes", new DialogInterface.OnClickListener() {
+ @Override
+ public void onClick(DialogInterface dialog, int which) {
+ ApplicationSettings.this.finish();
+ }
+ });
+ builder.setNegativeButton("No", new DialogInterface.OnClickListener() {
+ @Override
+ public void onClick(DialogInterface dialog, int which) {
+ }
+ });
+ builder.show();
+ }
+
+
+ @Override
+ protected void onDestroy() {
+ super.onDestroy();
+ setResult(RESULT_OK, parentIntent);
+ }
+
+
+
+ @SuppressLint("DefaultLocale")
+ private void loadPermissionsList() throws NameNotFoundException {
+
+ permsList.clear();
+ disabledPermissions.clear();
+
+ PackageManager pm = getPackageManager();
+ PackageInfo pkgInfo = pm.getPackageInfo(pkgName, PackageManager.GET_PERMISSIONS);
+ if (pkgInfo.sharedUserId != null) {
+ ((CheckBox) findViewById(R.id.chkRevokePerms)).setEnabled(false);
+ ((CheckBox) findViewById(R.id.chkRevokePerms)).setChecked(false);
+ ((CheckBox) findViewById(R.id.chkRevokePerms)).setText("Shared packages not yet supported");
+ ((CheckBox) findViewById(R.id.chkRevokePerms)).setTextColor(Color.RED);
+ }
+ String[] permissions = pkgInfo.requestedPermissions;
+ if (permissions == null) {
+ permissions = new String[0];
+ }
+ for (String perm : permissions) {
+ if (perm.startsWith(PackagePermissions.REVOKED_PREFIX)) {
+ perm = perm.substring(PackagePermissions.REVOKED_PREFIX.length());
+ }
+ try {
+ permsList.add(pm.getPermissionInfo(perm, 0));
+ } catch (NameNotFoundException e) {
+ PermissionInfo unknownPerm = new PermissionInfo();
+ unknownPerm.name = perm;
+ permsList.add(unknownPerm);
+ }
+ }
+ disabledPermissions = prefs.getStringSet(pkgName + XposedMod.PREF_REVOKELIST, new HashSet());
+
+ Collections.sort(permsList, new Comparator() {
+ @Override
+ public int compare(PermissionInfo lhs, PermissionInfo rhs) {
+ if (lhs.name == null) {
+ return -1;
+ } else if (rhs.name == null) {
+ return 1;
+ } else {
+ return lhs.name.toUpperCase().compareTo(rhs.name.toUpperCase());
+ }
+ }
+ });
+ }
+
+
+ class PermsListAdaptor extends ArrayAdapter {
+
+ public PermsListAdaptor(Context context, List items) {
+
+ super(context, R.layout.app_list_item, items);
+ }
+
+ private class ViewHolder {
+ TextView tvName;
+ TextView tvDescription;
+ CheckBox chkPermDisabled;
+ }
+
+ public View getView(int position, View convertView, ViewGroup parent) {
+ View row = convertView;
+ ViewHolder vHolder;
+ if (row == null) {
+ row = getLayoutInflater().inflate(R.layout.app_permission_item, parent, false);
+ vHolder = new ViewHolder();
+ vHolder.tvName = (TextView) row.findViewById(R.id.perm_name);
+ vHolder.tvDescription = (TextView) row.findViewById(R.id.perm_description);
+ vHolder.chkPermDisabled = (CheckBox) row.findViewById(R.id.chkPermDisabled);
+ row.setTag(vHolder);
+ } else {
+ vHolder = (ViewHolder) row.getTag();
+ }
+
+ PermissionInfo perm = permsList.get(position);
+
+ CharSequence label = perm.loadLabel(getPackageManager());
+ if (!label.equals(perm.name)) {
+ label = perm.name + " (" + label + ")";
+ }
+
+ vHolder.tvName.setText(label);
+ vHolder.tvDescription.setText(perm.loadDescription(getPackageManager()));
+
+ vHolder.chkPermDisabled.setVisibility(View.GONE);
+
+ vHolder.tvName.setTag(perm.name);
+ if (disabledPermissions.contains(perm.name)) {
+ vHolder.tvName.setPaintFlags(vHolder.tvName.getPaintFlags() | Paint.STRIKE_THRU_TEXT_FLAG);
+ vHolder.tvName.setTextColor(Color.MAGENTA);
+ } else {
+ vHolder.tvName.setPaintFlags(vHolder.tvName.getPaintFlags() & (~Paint.STRIKE_THRU_TEXT_FLAG));
+ vHolder.tvName.setTextColor(Color.WHITE);
+ }
+ row.setOnClickListener(new View.OnClickListener() {
+
+ @Override
+ public void onClick(View v) {
+ if (!allowRevoking)
+ return;
+
+ dirty = true;
+
+ TextView tv = (TextView) v.findViewById(R.id.perm_name);
+ if ((tv.getPaintFlags() & Paint.STRIKE_THRU_TEXT_FLAG) != 0) {
+ disabledPermissions.remove(tv.getTag());
+ tv.setPaintFlags(tv.getPaintFlags() & (~Paint.STRIKE_THRU_TEXT_FLAG));
+ tv.setTextColor(Color.WHITE);
+ } else {
+ disabledPermissions.add((String) tv.getTag());
+ tv.setPaintFlags(tv.getPaintFlags() | Paint.STRIKE_THRU_TEXT_FLAG);
+ tv.setTextColor(Color.MAGENTA);
+ }
+ }
+ });
+
+ return row;
+ }
+
+ }
+
+
+
+ @Override
+ public boolean onCreateOptionsMenu(Menu menu) {
+ getMenuInflater().inflate(R.menu.menu_app, menu);
+
+ if (getPackageManager().getLaunchIntentForPackage(pkgName) == null) {
+ menu.findItem(R.id.menu_app_launch).setEnabled(false);
+ Drawable icon = menu.findItem(R.id.menu_app_launch).getIcon().mutate();
+ icon.setColorFilter(Color.GRAY, Mode.SRC_IN);
+ menu.findItem(R.id.menu_app_launch).setIcon(icon);
+ }
+ return true;
+ }
+
+ @Override
+ public boolean onOptionsItemSelected(MenuItem item) {
+
+ if (item.getItemId() == R.id.menu_save) {
+ Editor prefsEditor = prefs.edit();
+ if (((Switch) findViewById(R.id.switchAppTweaked)).isChecked()) {
+ prefsEditor.putBoolean(pkgName + XposedMod.PREF_ACTIVE, true);
+ int dpi;
+ try {
+ dpi = Integer.parseInt(((EditText) findViewById(R.id.txtDPI)).getText().toString());
+ } catch (Exception ex) {
+ dpi = 0;
+ }
+ if (dpi != 0) {
+ prefsEditor.putInt(pkgName + XposedMod.PREF_DPI, dpi);
+ } else {
+ prefsEditor.remove(pkgName + XposedMod.PREF_DPI);
+ }
+ int screen = ((Spinner) findViewById(R.id.spnScreen)).getSelectedItemPosition();
+ if (screen > 0) {
+ prefsEditor.putInt(pkgName + XposedMod.PREF_SCREEN, screen);
+ } else {
+ prefsEditor.remove(pkgName + XposedMod.PREF_SCREEN);
+ }
+ if (((CheckBox) findViewById(R.id.chkTablet)).isChecked()) {
+ prefsEditor.putBoolean(pkgName + XposedMod.PREF_TABLET, true);
+ } else {
+ prefsEditor.remove(pkgName + XposedMod.PREF_TABLET);
+ }
+ if (((CheckBox) findViewById(R.id.chkResident)).isChecked()) {
+ prefsEditor.putBoolean(pkgName + XposedMod.PREF_RESIDENT, true);
+ } else {
+ prefsEditor.remove(pkgName + XposedMod.PREF_RESIDENT);
+ }
+ prefsEditor.remove(pkgName + XposedMod.PREF_REVOKELIST);
+ if (disabledPermissions.size() > 0) {
+ prefsEditor.putStringSet(pkgName + XposedMod.PREF_REVOKELIST, disabledPermissions);
+ }
+ if (((CheckBox) findViewById(R.id.chkRevokePerms)).isChecked()) {
+ prefsEditor.putBoolean(pkgName + XposedMod.PREF_REVOKEPERMS, true);
+ } else {
+ prefsEditor.remove(pkgName + XposedMod.PREF_REVOKEPERMS);
+ }
+ selectedLocale = ((Spinner) findViewById(R.id.spnLanguage)).getSelectedItemPosition();
+ if (selectedLocale > 0) {
+ prefsEditor.putString(pkgName + XposedMod.PREF_LOCALE, localeCodes[selectedLocale]);
+ } else {
+ prefsEditor.remove(pkgName + XposedMod.PREF_LOCALE);
+ }
+
+ } else {
+ prefsEditor.remove(pkgName + XposedMod.PREF_ACTIVE);
+ prefsEditor.remove(pkgName + XposedMod.PREF_RESIDENT);
+ prefsEditor.remove(pkgName + XposedMod.PREF_REVOKEPERMS);
+ prefsEditor.remove(pkgName + XposedMod.PREF_REVOKELIST);
+ prefsEditor.remove(pkgName + XposedMod.PREF_DPI);
+ prefsEditor.remove(pkgName + XposedMod.PREF_SCREEN);
+ prefsEditor.remove(pkgName + XposedMod.PREF_TABLET);
+ prefsEditor.remove(pkgName + XposedMod.PREF_LOCALE);
+ }
+ prefsEditor.commit();
+
+ dirty = false;
+
+ // Check if in addition so saving the settings, the app should also be killed
+ AlertDialog.Builder builder = new AlertDialog.Builder(this);
+ builder.setTitle("Apply settings");
+ builder.setMessage("Also kill the application so when it's relaunched it uses the new settings?");
+ builder.setPositiveButton("Yes", new DialogInterface.OnClickListener() {
+ @Override
+ public void onClick(DialogInterface dialog, int which) {
+ // Send the broadcast requesting to kill the app
+ Intent applyIntent = new Intent(XposedMod.MY_PACKAGE_NAME + ".UPDATE_PERMISSIONS");
+ applyIntent.putExtra("action", XposedMod.ACTION_PERMISSIONS);
+ applyIntent.putExtra("Package", pkgName);
+ applyIntent.putExtra("Kill", true);
+ sendBroadcast(applyIntent, XposedMod.MY_PACKAGE_NAME + ".BROADCAST_PERMISSION");
+
+ dialog.dismiss();
+ }
+ });
+ builder.setNegativeButton("No", new DialogInterface.OnClickListener() {
+ @Override
+ public void onClick(DialogInterface dialog, int which) {
+ // Send the broadcast but not requesting kill
+ Intent applyIntent = new Intent(XposedMod.MY_PACKAGE_NAME + ".UPDATE_PERMISSIONS");
+ applyIntent.putExtra("action", XposedMod.ACTION_PERMISSIONS);
+ applyIntent.putExtra("Package", pkgName);
+ applyIntent.putExtra("Kill", false);
+ sendBroadcast(applyIntent, XposedMod.MY_PACKAGE_NAME + ".BROADCAST_PERMISSION");
+
+ dialog.dismiss();
+ }
+ });
+ builder.create().show();
+
+ } else if (item.getItemId() == R.id.menu_app_launch) {
+ Intent LaunchIntent = getPackageManager().getLaunchIntentForPackage(pkgName);
+ startActivity(LaunchIntent);
+ } else if (item.getItemId() == R.id.menu_app_settings) {
+ startActivity(new Intent(android.provider.Settings.ACTION_APPLICATION_DETAILS_SETTINGS,
+ Uri.parse("package:" + pkgName)));
+ }
+ return super.onOptionsItemSelected(item);
+ }
+
+
+
+ /*
+ * From AOSP code - listing available languages to present to the user
+ */
+ private static class LocaleInfo implements Comparable {
+ static final Collator sCollator = Collator.getInstance();
+
+ String label;
+ Locale locale;
+
+ public LocaleInfo(String label, Locale locale) {
+ this.label = label;
+ this.locale = locale;
+ }
+
+ @Override
+ public String toString() {
+ return this.label;
+ }
+
+ @Override
+ public int compareTo(LocaleInfo another) {
+ return sCollator.compare(this.label, another.label);
+ }
+ }
+
+
+ private void prepareLocalesList() {
+ final String[] locales = Resources.getSystem().getAssets().getLocales();
+ Arrays.sort(locales);
+ final int origSize = locales.length;
+ final LocaleInfo[] preprocess = new LocaleInfo[origSize];
+ int finalSize = 0;
+ for (int i = 0 ; i < origSize; i++ ) {
+ final String s = locales[i];
+ final int len = s.length();
+ if (len == 5) {
+ String language = s.substring(0, 2);
+ String country = s.substring(3, 5);
+ final Locale l = new Locale(language, country);
+
+ if (finalSize == 0) {
+ preprocess[finalSize++] =
+ new LocaleInfo(toTitleCase(l.getDisplayLanguage(l)), l);
+ } else {
+ // check previous entry:
+ // same lang and a country -> upgrade to full name and
+ // insert ours with full name
+ // diff lang -> insert ours with lang-only name
+ if (preprocess[finalSize-1].locale.getLanguage().equals(
+ language)) {
+ preprocess[finalSize-1].label = toTitleCase(
+ getDisplayName(preprocess[finalSize-1].locale));
+ preprocess[finalSize++] =
+ new LocaleInfo(toTitleCase(
+ getDisplayName(l)), l);
+ } else {
+ String displayName;
+ if (s.equals("zz_ZZ")) {
+ displayName = "Pseudo...";
+ } else {
+ displayName = toTitleCase(l.getDisplayLanguage(l));
+ }
+ preprocess[finalSize++] = new LocaleInfo(displayName, l);
+ }
+ }
+ }
+ }
+
+ final LocaleInfo[] localeInfos = new LocaleInfo[finalSize];
+ for (int i = 0; i < finalSize; i++) {
+ localeInfos[i] = preprocess[i];
+ }
+ Arrays.sort(localeInfos);
+
+ String configuredLang = prefs.getString(pkgName + XposedMod.PREF_LOCALE, null);
+ selectedLocale = 0;
+
+ localeCodes = new String[localeInfos.length + 1];
+ localeDescriptions = new String[localeInfos.length + 1];
+ localeCodes[0] = "";
+ localeDescriptions[0] = "(Default)";
+ for (int i = 1; i < finalSize + 1; i++) {
+ localeCodes[i] = getLocaleCode(localeInfos[i-1].locale);
+ localeDescriptions[i] = localeInfos[i-1].label;
+ if (localeCodes[i].equals(configuredLang))
+ selectedLocale = i;
+ }
+ }
+
+ private static String toTitleCase(String s) {
+ if (s.length() == 0) {
+ return s;
+ }
+
+ return Character.toUpperCase(s.charAt(0)) + s.substring(1);
+ }
+
+ private static String getDisplayName(Locale loc) {
+ return loc.getDisplayName(loc);
+ }
+
+ private static String getLocaleCode(Locale loc) {
+ String result = loc.getLanguage();
+ if (loc.getCountry().length() > 0)
+ result += "_" + loc.getCountry();
+ if (loc.getVariant().length() > 0)
+ result += "_" + loc.getVariant();
+ return result;
+ }
+}