diff --git a/.classpath b/.classpath
new file mode 100644
index 0000000..a4763d1
--- /dev/null
+++ b/.classpath
@@ -0,0 +1,8 @@
+
+
+
+
+
+
+
+
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..fef33df
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,15 @@
+# Files for the dex VM.
+*.dex
+
+# Java class files.
+*.class
+
+# Generated files.
+bin/
+gen/
+
+# Local configuration file (sdk path, etc).
+local.properties
+
+# Vim.
+*.sw*
diff --git a/.project b/.project
new file mode 100644
index 0000000..643a2a9
--- /dev/null
+++ b/.project
@@ -0,0 +1,33 @@
+
+
+ chromeview
+
+
+
+
+
+ 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..9bf2876
--- /dev/null
+++ b/.settings/org.eclipse.jdt.core.prefs
@@ -0,0 +1,281 @@
+eclipse.preferences.version=1
+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=16
+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=16
+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=16
+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=do not 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=2
+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=80
+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=space
+org.eclipse.jdt.core.formatter.tabulation.size=2
+org.eclipse.jdt.core.formatter.use_on_off_tags=false
+org.eclipse.jdt.core.formatter.use_tabs_only_for_leading_indentations=false
+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..998a281
--- /dev/null
+++ b/.settings/org.eclipse.jdt.ui.prefs
@@ -0,0 +1,3 @@
+eclipse.preferences.version=1
+formatter_profile=_NetMap
+formatter_settings_version=12
diff --git a/AndroidManifest.xml b/AndroidManifest.xml
new file mode 100644
index 0000000..45f529c
--- /dev/null
+++ b/AndroidManifest.xml
@@ -0,0 +1,9 @@
+
+
+
+
\ No newline at end of file
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..0777126
--- /dev/null
+++ b/README.md
@@ -0,0 +1,179 @@
+# ChromeView
+
+ChormeView works like Android's WebView, but is backed by the latest Chromium
+code.
+
+
+## Why ChromeView
+
+ChromeView lets you ship your own Chromium code, instead of using whatever
+version comes with your user's Android image. This gives your application
+early access to the newest features in Chromium, and removes the variability
+due to different WebView implementations in different versions of Android.
+
+
+## Setting Up
+
+This section explains how to set up your Android project to use ChromeView.
+
+### Get the Code
+
+Check out the repository in your Eclipse workspace, and make your project use
+ChromeView as a library. In Eclipse, right-click your project directory, select
+`Properties`, choose the `Android` category, and click on the `Add` button in
+the `Library section`.
+
+### Copy Data
+
+Copy `assets/webviewchromium.pak` to your project's `assets` directory.
+[Star this bug](https://code.google.com/p/android/issues/detail?id=35748) if
+you agree that this is annoying.
+
+In your `Application` subclass, call `ChromeView.initialize` and pass it the
+application's context. For example,
+
+### Initialize Chromium
+
+```java
+import us.costan.chrome.ChromeView;
+import android.app.Application;
+
+public class MyApplication extends Application {
+ @Override
+ public void onCreate() {
+ super.onCreate();
+ ChromeView.initialize(this);
+ }
+}
+```
+
+Now you can use ChromeView in the same contexts as you would use WebView.
+
+### Star some bugs
+
+If you use this project and want to help move it along, please star the
+following bugs.
+
+* [crbug.com/113088](http://crbug.com/113088)
+* [crbug.com/234907](http://crbug.com/234907) and
+* [this Android bug](https://code.google.com/p/android/issues/detail?id=35748)
+
+
+## Usage
+
+To access ChromeView in the graphical layout editor, go to the `Palette`,
+expand the `Custom and Library Views` section, and click the `Refresh` button.
+
+ChromeView supports most of the WebView methods. For example,
+
+```java
+ChromeView chromeView = (ChromeView)findViewById(R.id.gameUiView);
+chromeView.getSettings().setJavaScriptEnabled(true);
+chromeView.loadUrl("http://www.google.com");
+```
+
+### JavaScript
+
+ChromeView's `addJavaScriptInterface` exposes public methods that are annotated
+with `@ChromeJavascriptInterface`. This is because WebView's
+`@JavascriptInterface` is only available on Android 4.2 and above, but
+ChromeView targets 4.0 and 4.1 as well.
+
+```java
+import us.costan.chrome.ChromeJavascriptInterface;
+
+public class JsBindings {
+ @ChromeJavascriptInterface
+ public String getHello() {
+ return "Hello world";
+ }
+}
+
+chromeView.addJavascriptInterface(new JsBindings(), "AndroidBindings");
+```
+
+### Cookies
+
+ChromeCookieManager is ChromeView's equivalent of CookieManager.
+
+```java
+ChromeCookieManager.getInstance().getCookie("https://www.google.com");
+```
+
+### Faster Development
+
+To speed up the application launch on real devices, remove the `libs/x86`
+directory. When developing on Atom devices, remove the ARM directory instead.
+
+Remember to `git checkout -- .` and get the library back before building a
+release APK.
+
+### Internet Access
+
+If your application manifest doesn't specify the
+[INTERNET permission](http://developer.android.com/reference/android/Manifest.permission.html#INTERNET),
+the Chromium code behind ChromeView silentely blocks all network requests. This
+is mentioned here because it can be hard to debug.
+
+
+## Building
+
+The bulk of this project is Chromium source code and build products. With the
+appropriate infrastructure, the Chromium bits can be easily updated.
+
+[crbuild/vm-build.md](crbuild/vm-build.md) contains step-by-step instructions
+for setting up a VM and building the Chromium for Android components used by
+ChromeView.
+
+Once Chromium has been successfully built, running
+[crbuild/update.sh](crbuild/update.sh) will copy the relevant bits from the
+build VM into the ChromeView source tree.
+
+
+## Issues
+
+Attempting to scroll the view (by swiping a finger across the screen) does not
+update the displayed image. However, internally, the view is scrolled. This can
+be seen by displaying a stack of buttons and trying to click on the topmost
+one. This issue makes ChromeView mostly unusable in production.
+
+The core issue is that the integration is done via `AwContent` in the
+`android_webview` directory of the Chromium source tree, which is experimental
+and not intended for embedding use. The "right" way of doing this is to embed
+a `ContentView` from the `content` directory, or a `Shell` in `content/shell`.
+Unfortunately, these components' APIs don't match WebView nearly as well as
+AwContent, and they're much harder to integrate. Pull requests or a fork would
+be welcome.
+
+This repository is rebased often, because the large files in `lib/` would
+result in a huge repository if new commits were created for each build. The
+large files are Chromium build products.
+
+
+## Contributing
+
+Please don't hesitate to send your Pull Requests!
+
+Please don't send pull requests including the binary assets or code extracted
+from Android (`assets/`, `libs/`, `src/com/googlecode/` and `src/org/android`).
+If your Pull Request requires updated Android bits, mention that in the PR
+description, and I will rebuild the Android bits.
+
+
+## Copyright and License
+
+The directories below contain code from the
+[The Chromium Project](http://www.chromium.org/), which is subject to the
+copyright and license on the project site.
+
+* `assets/`
+* `libs/`
+* `src/com/googlecode`
+* `src/org/chromium`
+
+Some of the source code in `src/us/costan/chrome` has been derived from the
+Android source code, and is therefore covered by the
+[Android project licenses](http://source.android.com/source/licenses.html).
+
+The rest of the code is Copyright 2013, Victor Costan, and available under the
+MIT license.
diff --git a/assets/webviewchromium.pak b/assets/webviewchromium.pak
new file mode 100644
index 0000000..2ac39bd
Binary files /dev/null and b/assets/webviewchromium.pak differ
diff --git a/crbuild/update.sh b/crbuild/update.sh
new file mode 100755
index 0000000..84f51eb
--- /dev/null
+++ b/crbuild/update.sh
@@ -0,0 +1,59 @@
+# Updates this project with the Chrome build files.
+# This script assumes the Chrome build VM is up at crbuild.local
+
+# Clean up.
+rm -r assets/*
+rm -r libs/*
+rm -r src/com/googlecode
+rm -r src/org/chromium
+
+# ContentShell core -- use this if android_webview doesn't work out.
+#scp crbuild@crbuild.local:chromium/src/out/Release/content_shell/assets/* \
+# assets/
+#scp -r crbuild@crbuild.local:chromium/src/out/Release/content_shell_apk/libs/* \
+# libs
+#scp -r crbuild@crbuild.local:chromium/src/content/shell/android/java/res/* res
+#scp -r crbuild@crbuild.local:chromium/src/content/shell/android/java/src/* src
+#scp -r crbuild@crbuild.local:chromium/src/content/shell_apk/android/java/res/* res
+
+# android_webview
+scp crbuild@crbuild.local:chromium/src/out/Release/android_webview_apk/assets/*.pak \
+ assets
+scp -r crbuild@crbuild.local:chromium/src/out/Release/android_webview_apk/libs/* \
+ libs
+rm libs/**/gdbserver
+scp -r crbuild@crbuild.local:chromium/src/android_webview/java/src/* src/
+
+## Dependencies inferred from android_webview/Android.mk
+
+# Resources.
+scp -r crbuild@crbuild.local:chromium/src/content/public/android/java/resource_map/* src/
+scp -r crbuild@crbuild.local:chromium/src/ui/android/java/resource_map/* src/
+
+# ContentView dependencies.
+scp -r crbuild@crbuild.local:chromium/src/base/android/java/src/* src/
+scp -r crbuild@crbuild.local:chromium/src/content/public/android/java/src/* src/
+scp -r crbuild@crbuild.local:chromium/src/media/base/android/java/src/* src/
+scp -r crbuild@crbuild.local:chromium/src/net/android/java/src/* src/
+scp -r crbuild@crbuild.local:chromium/src/ui/android/java/src/* src/
+scp -r crbuild@crbuild.local:chromium/src/third_party/eyesfree/src/android/java/src/* src/
+
+# Strip a ContentView file that's not supposed to be here.
+rm src/org/chromium/content/common/common.aidl
+
+# Get rid of the .svn directory in eyesfree.
+rm -r src/com/googlecode/eyesfree/braille/.svn
+
+# Browser components.
+scp -r crbuild@crbuild.local:chromium/src/components/web_contents_delegate_android/android/java/src/* src/
+scp -r crbuild@crbuild.local:chromium/src/components/navigation_interception/android/java/src/* src/
+
+# Generated files.
+scp -r crbuild@crbuild.local:chromium/src/out/Release/gen/templates/* src/
+
+# JARs.
+scp -r crbuild@crbuild.local:chromium/src/out/Release/lib.java/guava_javalib.jar libs/
+scp -r crbuild@crbuild.local:chromium/src/out/Release/lib.java/jsr_305_javalib.jar libs/
+
+# android_webview generated sources. Must come after all the other sources.
+scp -r crbuild@crbuild.local:chromium/src/android_webview/java/generated_src/* src/
diff --git a/crbuild/vm-build.md b/crbuild/vm-build.md
new file mode 100644
index 0000000..9e25539
--- /dev/null
+++ b/crbuild/vm-build.md
@@ -0,0 +1,126 @@
+# VM Setup Instructions
+
+This library's repository includes some Chromium files that are painful to
+build. Chromium's build process is a bit fussy, and the Android target is even
+more fussy, so the least painful way of getting it done is to set up a VM with
+the exact software that the build process was designed for.
+
+This document contains step-by-step instructions for setting up the build VM
+and building the files used in this library.
+
+
+## VM Building
+
+These are the manual steps for setting up a VM. They only need to be done once.
+
+1. Get the 64-bit ISO for Ubuntu Server 12.10.
+ * Go to http://releases.ubuntu.com/12.10/
+ * Get the `64-bit PC (AMD64) server install image`
+
+2. Set up a VirtualBox VM.
+ * Name: ChromeWebView
+ * Type: Linux
+ * Version: Ubuntu 64-bit
+ * RAM: 4096Mb
+ * Disk: VDI, dynamic, 48Gb
+
+3. Change the settings (Machine > Settings in the VirtualBox menu)
+ * System > Processor > Processor(s): 4 (number of CPU cores on the machine)
+ * Audio > uncheck Enable Audio
+ * Network > Adapter 1 > Advanced > Adapter Type: virtio-net
+ * Network > Adapter 2
+ * check Enable network adapter
+ * Attached to > Host-only Adapter
+ * Advanced > Adapter Type: virtio-net
+ * Ports > USB > uncheck Enable USB 2.0 (EHCI) Controller
+
+4. Start VM and set up the server.
+ * Select the Ubuntu ISO downloaded earlier.
+ * Start a server installation, providing default answers, except:
+ * Hostname: crbuild
+ * Full name: crbuild
+ * Username: crbuild
+ * Password: crbuild
+ * Confirm using a weak password
+ * Encrypt home directory: no
+ * Partitioning: Guided - use entire disk (no LVM or encryption)
+ * Software to install: OpenSSH server
+
+6. After the VM restarts, set up networking.
+ * Log in using the VM console.
+ * Open /etc/network/interfaces in a text editor (sudo vim ...)
+ * Duplicate the "primary network interface" section
+ * In the duplicate section, replace-all eth0 with eth1, primary with
+ secondary
+ * Save the file.
+ * `sudo apt-get install -y avahi-daemon`
+ * `sudo reboot`
+
+7. Prepare to SSH into the VM.
+ * If you don't have an ssh key
+ * `ssh-keygen -t rsa`
+ * press Enter all the way (default key type, no passphrase)
+
+ ```bash
+ ssh-copy-id crbuild@crbuild.local
+ ssh crbuild@crbuild.local
+ ```
+
+8. Get the Oracle Java 6 JDK.
+ * Go to http://www.oracle.com/technetwork/java/javase/downloads/index.html
+ * Search for "Java SE 6", click on JDK
+ * Click on the radio button for accepting the license
+ * Download the Linux x86 non-RPM file (jdk-6uNN-linux-x64.bin)
+
+ ```bash
+ exit # Get out of the VM ssh session, run this on the host.
+ scp ~/Downloads/jdk-6u*-linux-x64.bin crbuild@crbuild.local:~/jdk6.bin
+ ```
+
+9. Set up the VM builds target platform(s). Choosing more than one platform
+ will result in no incremental building being done.
+
+ ```bash
+ # ssh crbuild@crbuild.local
+ touch ~/.build_arm
+ touch ~/.build_x86
+ ```
+
+10. Optionally set the path of the directory that will hold the Chromium source
+ code to. This is only used the first time the setup script runs, so it only
+ needs to be set once.
+
+ ```bash
+ export CHROMIUM_DIR=/mnt/chromium
+ ```
+
+
+## VM Setup
+
+The setup script will complete the work done above. The script is idempotent,
+so it can be ran to bring a VM's software up to date.
+
+```bash
+# ssh crbuild@crbuild.local
+curl -fLsS https://github.com/pwnall/chromeview/raw/master/crbuild/vm-setup.sh | sh
+```
+
+If the script fails, the steps in [vm-setup.sh](crbuild/vm-setup.sh) can be
+copy-pasted one by one in the VM's ssh console. Please open an issue or pull
+request if the script fails.
+
+If this is the first time the Chromium source code is downloaded and
+`$CHROMIUM_DIR` is defined, the directory at that path will be created and
+symlinked into `~/chromium`. The source code can be moved around, as long as
+the symlink is updated.
+
+
+## Building
+
+The build script will update the Chromium source code and do a build. For
+infrequent builds, the setup script above should be ran before a build.
+
+```bash
+# ssh crbuild@crbuild.local
+curl -fLsS https://github.com/pwnall/chromeview/raw/master/crbuild/vm-build.sh | sh
+```
diff --git a/crbuild/vm-build.sh b/crbuild/vm-build.sh
new file mode 100644
index 0000000..2239bb4
--- /dev/null
+++ b/crbuild/vm-build.sh
@@ -0,0 +1,24 @@
+#!/bin/sh
+# Builds the Chromium bits needed by ChromeView.
+
+set -o errexit # Stop the script on the first error.
+set -o nounset # Catch un-initialized variables.
+
+cd ~/chromium/
+# https://code.google.com/p/chromium/wiki/UsingGit
+gclient sync --jobs 16
+cd ~/chromium/src
+
+if [ -f ~/.build_android ] ; then
+ . build/android/envsetup.sh --target-arch=arm
+ android_gyp
+ ninja -C out/Release -k0 -j$CPUS libwebviewchromium android_webview_apk \
+ content_shell_apk chromium_testshell
+fi
+
+if [ -f ~/.build_x86 ] ; then
+ . build/android/envsetup.sh --target-arch=x86
+ android_gyp
+ ninja -C out/Release -k0 -j$CPUS libwebviewchromium android_webview_apk \
+ content_shell_apk chromium_testshell
+fi
diff --git a/crbuild/vm-setup.sh b/crbuild/vm-setup.sh
new file mode 100755
index 0000000..76a57f3
--- /dev/null
+++ b/crbuild/vm-setup.sh
@@ -0,0 +1,112 @@
+#!/bin/sh
+# Idempotent VM setup / upgrade script.
+
+set -o errexit # Stop the script on the first error.
+set -o nounset # Catch un-initialized variables.
+
+# Enable password-less sudo for the current user.
+sudo sh -c "echo '$USER ALL=(ALL:ALL) NOPASSWD: ALL' > /etc/sudoers.d/$USER"
+
+# Sun JDK 6.
+if [ ! -f /usr/bin/javac ] ; then
+ if [ ! -f ~/jdk6.bin ] ; then
+ echo 'Please download the Linux x86 non-RPM JDK6 as jdk6.bin from'
+ echo 'http://www.oracle.com/technetwork/java/javase/downloads/index.html'
+ exit 1
+ fi
+
+ sudo mkdir -p /usr/lib/jvm
+ cd /usr/lib/jvm
+ sudo /bin/sh ~/jdk6.bin -noregister
+ rm ~/jdk6.bin
+ sudo update-alternatives --install /usr/bin/javac javac \
+ /usr/lib/jvm/jdk1.6.0_*/bin/javac 50000
+ sudo update-alternatives --config javac
+ sudo update-alternatives --install /usr/bin/java java \
+ /usr/lib/jvm/jdk1.6.0_*/bin/java 50000
+ sudo update-alternatives --config java
+ sudo update-alternatives --install /usr/bin/javaws javaws \
+ /usr/lib/jvm/jdk1.6.0_*/bin/javaws 50000
+ sudo update-alternatives --config javaws
+ sudo update-alternatives --install /usr/bin/javap javap \
+ /usr/lib/jvm/jdk1.6.0_*/bin/javap 50000
+ sudo update-alternatives --config javap
+ sudo update-alternatives --install /usr/bin/jar jar \
+ /usr/lib/jvm/jdk1.6.0_*/bin/jar 50000
+ sudo update-alternatives --config jar
+ sudo update-alternatives --install /usr/bin/jarsigner jarsigner \
+ /usr/lib/jvm/jdk1.6.0_*/bin/jarsigner 50000
+ sudo update-alternatives --config jarsigner
+ cd ~/
+fi
+
+# Update all system packages.
+sudo apt-get update -qq
+sudo apt-get -y dist-upgrade
+
+# debconf-get-selections is useful for figuring out debconf defaults.
+sudo apt-get install -y debconf-utils
+
+# Quiet all package installation prompts.
+sudo debconf-set-selections <<'END'
+debconf debconf/frontend select Noninteractive
+debconf debconf/priority select critical
+END
+
+# Git.
+sudo apt-get install -y git
+
+# Depot tools.
+# http://dev.chromium.org/developers/how-tos/install-depot-tools
+cd ~
+if [ -d ~/depot_tools ] ; then
+ cd ~/depot_tools
+ git pull origin master
+ cd ~
+fi
+if [ ! -d ~/depot_tools ] ; then
+ git clone https://chromium.googlesource.com/chromium/tools/depot_tools.git
+fi
+if ! grep -q 'export PATH=$PATH:$HOME/depot_tools' ~/.bashrc ; then
+ echo 'export PATH=$PATH:$HOME/depot_tools' >> ~/.bashrc
+ export PATH=$PATH:$HOME/depot_tools
+fi
+
+# Subversion and git-svn.
+sudo apt-get install -y git-svn subversion
+
+# Chromium source.
+# https://code.google.com/p/chromium/wiki/UsingGit
+# http://dev.chromium.org/developers/how-tos/get-the-code
+if [ ! -d ~/chromium ] ; then
+ if [ ! -z $CHROMIUM_DIR ] ; then
+ sudo mkdir -p "$CHROMIUM_DIR"
+ sudo chown $USER "$CHROMIUM_DIR"
+ chmod 0755 "$CHROMIUM_DIR"
+ ln -s "$CHROMIUM_DIR" ~/chromium
+ fi
+ if [ -z "$CHROMIUM_DIR" ] ; then
+ mkdir -p ~/chromium
+ fi
+fi
+cd ~/chromium
+if [ ! -f .gclient ] ; then
+ ~/depot_tools/fetch android --nosvn=True || \
+ echo "Ignore the error above if this is a first-time setup"
+fi
+if ! grep '"safesync_url":' .gclient ; then
+ echo -n '' # Noop
+ # TODO(pwnall): do this right
+ # echo '"safesync_url": "https://chromium-status.appspot.com/git-lkgr"' >> .gclient
+fi
+cd ~/chromium/src
+sudo ./build/install-build-deps-android.sh
+sudo ./build/install-build-deps.sh --no-syms --lib32 --arm --no-prompt
+gclient runhooks
+
+# Chromium build setup.
+# https://code.google.com/p/chromium/wiki/LinuxBuildInstructions
+# https://code.google.com/p/chromium/wiki/AndroidBuildInstructions
+
+# Chromium build depedenecies not covered by the Chromium scripts.
+sudo apt-get install -y ia32-libs libc6-dev-i386 g++-multilib
diff --git a/libs/armeabi-v7a/libwebviewchromium.so b/libs/armeabi-v7a/libwebviewchromium.so
new file mode 100755
index 0000000..c476be5
Binary files /dev/null and b/libs/armeabi-v7a/libwebviewchromium.so differ
diff --git a/libs/guava_javalib.jar b/libs/guava_javalib.jar
new file mode 100644
index 0000000..24b4e2e
Binary files /dev/null and b/libs/guava_javalib.jar differ
diff --git a/libs/jsr_305_javalib.jar b/libs/jsr_305_javalib.jar
new file mode 100644
index 0000000..a2a6691
Binary files /dev/null and b/libs/jsr_305_javalib.jar differ
diff --git a/libs/x86/libwebviewchromium.so b/libs/x86/libwebviewchromium.so
new file mode 100755
index 0000000..4213ac2
Binary files /dev/null and b/libs/x86/libwebviewchromium.so differ
diff --git a/proguard-project.txt b/proguard-project.txt
new file mode 100644
index 0000000..f2fe155
--- /dev/null
+++ b/proguard-project.txt
@@ -0,0 +1,20 @@
+# To enable ProGuard in your project, edit project.properties
+# to define the proguard.config property as described in that file.
+#
+# Add project specific ProGuard rules here.
+# By default, the flags in this file are appended to flags specified
+# in ${sdk.dir}/tools/proguard/proguard-android.txt
+# You can edit the include path and order by changing the ProGuard
+# include property in project.properties.
+#
+# For more details, see
+# http://developer.android.com/guide/developing/tools/proguard.html
+
+# Add any project specific keep options here:
+
+# If your project uses WebView with JS, uncomment the following
+# and specify the fully qualified class name to the JavaScript interface
+# class:
+#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
+# public *;
+#}
diff --git a/project.properties b/project.properties
new file mode 100644
index 0000000..484dab0
--- /dev/null
+++ b/project.properties
@@ -0,0 +1,15 @@
+# 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-17
+android.library=true
diff --git a/res/raw/blank_html.html b/res/raw/blank_html.html
new file mode 100644
index 0000000..989fbfc
--- /dev/null
+++ b/res/raw/blank_html.html
@@ -0,0 +1,5 @@
+
+
+ This page intentionally left blank.
+
+
diff --git a/res/values/strings.xml b/res/values/strings.xml
new file mode 100644
index 0000000..2334481
--- /dev/null
+++ b/res/values/strings.xml
@@ -0,0 +1,5 @@
+
+
+
+ text/plain
+
\ No newline at end of file
diff --git a/src/com/googlecode/eyesfree/braille/display/BrailleDisplayProperties.aidl b/src/com/googlecode/eyesfree/braille/display/BrailleDisplayProperties.aidl
new file mode 100644
index 0000000..5a7f410
--- /dev/null
+++ b/src/com/googlecode/eyesfree/braille/display/BrailleDisplayProperties.aidl
@@ -0,0 +1,19 @@
+/*
+ * Copyright (C) 2012 Google Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+
+package com.googlecode.eyesfree.braille.display;
+
+parcelable BrailleDisplayProperties;
diff --git a/src/com/googlecode/eyesfree/braille/display/BrailleDisplayProperties.java b/src/com/googlecode/eyesfree/braille/display/BrailleDisplayProperties.java
new file mode 100644
index 0000000..606476f
--- /dev/null
+++ b/src/com/googlecode/eyesfree/braille/display/BrailleDisplayProperties.java
@@ -0,0 +1,128 @@
+/*
+ * Copyright (C) 2012 Google Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+
+package com.googlecode.eyesfree.braille.display;
+
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * Properties of a braille display such as dimensions and keyboard
+ * configuration.
+ */
+public class BrailleDisplayProperties implements Parcelable {
+ private final int mNumTextCells;
+ private final int mNumStatusCells;
+ private final BrailleKeyBinding[] mKeyBindings;
+ private final Map mFriendlyKeyNames;
+
+ public BrailleDisplayProperties(int numTextCells, int numStatusCells,
+ BrailleKeyBinding[] keyBindings,
+ Map friendlyKeyNames) {
+ mNumTextCells = numTextCells;
+ mNumStatusCells = numStatusCells;
+ mKeyBindings = keyBindings;
+ mFriendlyKeyNames = friendlyKeyNames;
+ }
+
+ /**
+ * Returns the number of cells on the main display intended for display of
+ * text or other content.
+ */
+ public int getNumTextCells() {
+ return mNumTextCells;
+ }
+
+ /**
+ * Returns the number of status cells that are separated from the main
+ * display. This value will be {@code 0} for displays without any separate
+ * status cells.
+ */
+ public int getNumStatusCells() {
+ return mNumStatusCells;
+ }
+
+ /**
+ * Returns the list of key bindings for this display.
+ */
+ public BrailleKeyBinding[] getKeyBindings() {
+ return mKeyBindings;
+ }
+
+ /**
+ * Returns an unmodifiable map mapping key names in {@link BrailleKeyBinding}
+ * objects to localized user-friendly key names.
+ */
+ public Map getFriendlyKeyNames() {
+ return mFriendlyKeyNames;
+ }
+
+ @Override
+ public String toString() {
+ return String.format(
+ "BrailleDisplayProperties [numTextCells: %d, numStatusCells: %d, "
+ + "keyBindings: %d]",
+ mNumTextCells, mNumStatusCells, mKeyBindings.length);
+ }
+
+ // For Parcelable support.
+
+ public static final Parcelable.Creator CREATOR =
+ new Parcelable.Creator() {
+ @Override
+ public BrailleDisplayProperties createFromParcel(Parcel in) {
+ return new BrailleDisplayProperties(in);
+ }
+
+ @Override
+ public BrailleDisplayProperties[] newArray(int size) {
+ return new BrailleDisplayProperties[size];
+ }
+ };
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(Parcel out, int flags) {
+ out.writeInt(mNumTextCells);
+ out.writeInt(mNumStatusCells);
+ out.writeTypedArray(mKeyBindings, flags);
+ out.writeInt(mFriendlyKeyNames.size());
+ for (Map.Entry entry : mFriendlyKeyNames.entrySet()) {
+ out.writeString(entry.getKey());
+ out.writeString(entry.getValue());
+ }
+ }
+
+ private BrailleDisplayProperties(Parcel in) {
+ mNumTextCells = in.readInt();
+ mNumStatusCells = in.readInt();
+ mKeyBindings = in.createTypedArray(BrailleKeyBinding.CREATOR);
+ int size = in.readInt();
+ Map names = new HashMap(size);
+ for (int i = 0; i < size; ++i) {
+ names.put(in.readString(), in.readString());
+ }
+ mFriendlyKeyNames = Collections.unmodifiableMap(names);
+ }
+}
diff --git a/src/com/googlecode/eyesfree/braille/display/BrailleInputEvent.aidl b/src/com/googlecode/eyesfree/braille/display/BrailleInputEvent.aidl
new file mode 100644
index 0000000..f64c080
--- /dev/null
+++ b/src/com/googlecode/eyesfree/braille/display/BrailleInputEvent.aidl
@@ -0,0 +1,19 @@
+/*
+ * Copyright (C) 2012 Google Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+
+package com.googlecode.eyesfree.braille.display;
+
+parcelable BrailleInputEvent;
diff --git a/src/com/googlecode/eyesfree/braille/display/BrailleInputEvent.java b/src/com/googlecode/eyesfree/braille/display/BrailleInputEvent.java
new file mode 100644
index 0000000..1c2ffb4
--- /dev/null
+++ b/src/com/googlecode/eyesfree/braille/display/BrailleInputEvent.java
@@ -0,0 +1,300 @@
+/*
+ * Copyright (C) 2012 Google Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+
+package com.googlecode.eyesfree.braille.display;
+
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.util.SparseArray;
+
+import java.util.HashMap;
+
+/**
+ * An input event, originating from a braille display.
+ *
+ * An event contains a command that is a high-level representation of the
+ * key or key combination that was pressed on the display such as a
+ * navigation key or braille keyboard combination. For some commands, there is
+ * also an integer argument that contains additional information.
+ */
+public class BrailleInputEvent implements Parcelable {
+
+ // Movement commands.
+
+ /** Keyboard command: Used when there is no actual command. */
+ public static final int CMD_NONE = -1;
+
+ /** Keyboard command: Navigate upwards. */
+ public static final int CMD_NAV_LINE_PREVIOUS = 1;
+ /** Keyboard command: Navigate downwards. */
+ public static final int CMD_NAV_LINE_NEXT = 2;
+ /** Keyboard command: Navigate left one item. */
+ public static final int CMD_NAV_ITEM_PREVIOUS = 3;
+ /** Keyboard command: Navigate right one item. */
+ public static final int CMD_NAV_ITEM_NEXT = 4;
+ /** Keyboard command: Navigate one display window to the left. */
+ public static final int CMD_NAV_PAN_LEFT = 5;
+ /** Keyboard command: Navigate one display window to the right. */
+ public static final int CMD_NAV_PAN_RIGHT = 6;
+ /** Keyboard command: Navigate to the top or beginning. */
+ public static final int CMD_NAV_TOP = 7;
+ /** Keyboard command: Navigate to the bottom or end. */
+ public static final int CMD_NAV_BOTTOM = 8;
+
+ // Activation commands.
+
+ /** Keyboard command: Activate the currently selected/focused item. */
+ public static final int CMD_ACTIVATE_CURRENT = 20;
+
+ // Scrolling.
+
+ /** Keyboard command: Scroll backward. */
+ public static final int CMD_SCROLL_BACKWARD = 30;
+ /** Keyboard command: Scroll forward. */
+ public static final int CMD_SCROLL_FORWARD = 31;
+
+ // Selection commands.
+
+ /** Keyboard command: Set the start ot the selection. */
+ public static final int CMD_SELECTION_START = 40;
+ /** Keyboard command: Set the end of the selection. */
+ public static final int CMD_SELECTION_END = 41;
+ /** Keyboard command: Select all content of the current field. */
+ public static final int CMD_SELECTION_SELECT_ALL = 42;
+ /** Keyboard command: Cut the content of the selection. */
+ public static final int CMD_SELECTION_CUT = 43;
+ /** Keyboard command: Copy the current selection. */
+ public static final int CMD_SELECTION_COPY = 44;
+ /**
+ * Keyboard command: Paste the content of the clipboard at the current
+ * insertion point.
+ */
+ public static final int CMD_SELECTION_PASTE = 45;
+
+ /**
+ * Keyboard command: Primary routing key pressed, typically
+ * used to move the insertion point or click/tap on the item
+ * under the key.
+ * The argument is the zero-based position, relative to the first cell
+ * on the display, of the cell that is closed to the key that
+ * was pressed.
+ */
+ public static final int CMD_ROUTE = 50;
+
+ // Braille keyboard input.
+
+ /**
+ * Keyboard command: A key combination was pressed on the braille
+ * keyboard.
+ * The argument contains the dots that were pressed as a bitmask.
+ */
+ public static final int CMD_BRAILLE_KEY = 60;
+
+ // Editing keys.
+
+ /** Keyboard command: Enter key. */
+ public static final int CMD_KEY_ENTER = 70;
+ /** Keyboard command: Delete backward. */
+ public static final int CMD_KEY_DEL = 71;
+ /** Keyboard command: Delete forward. */
+ public static final int CMD_KEY_FORWARD_DEL = 72;
+
+ // Glboal navigation keys.
+
+ /** Keyboard command: Back button. */
+ public static final int CMD_GLOBAL_BACK = 90;
+ /** Keyboard command: Home button. */
+ public static final int CMD_GLOBAL_HOME = 91;
+ /** Keyboard command: Recent apps button. */
+ public static final int CMD_GLOBAL_RECENTS = 92;
+ /** Keyboard command: Show notificaitons. */
+ public static final int CMD_GLOBAL_NOTIFICATIONS = 93;
+
+ // Miscelanous commands.
+
+ /** Keyboard command: Invoke keyboard help. */
+ public static final int CMD_HELP = 100;
+
+ // Meanings of the argument to a command.
+
+ /** This command doesn't have an argument. */
+ public static final int ARGUMENT_NONE = 0;
+ /**
+ * The lower order bits of the arguemnt to this command represent braille
+ * dots. Dot 1 is represented by the rightmost bit and so on until dot 8,
+ * which is represented by bit 7, counted from the right.
+ */
+ public static final int ARGUMENT_DOTS = 1;
+ /**
+ * The argument represents a 0-based position on the display counted from
+ * the leftmost cell.
+ */
+ public static final int ARGUMENT_POSITION = 2;
+
+ private static final SparseArray CMD_NAMES =
+ new SparseArray();
+ private static final HashMap NAMES_TO_CMDS
+ = new HashMap();
+ static {
+ CMD_NAMES.append(CMD_NAV_LINE_PREVIOUS, "CMD_NAV_LINE_PREVIOUS");
+ CMD_NAMES.append(CMD_NAV_LINE_NEXT, "CMD_NAV_LINE_NEXT");
+ CMD_NAMES.append(CMD_NAV_ITEM_PREVIOUS, "CMD_NAV_ITEM_PREVIOUS");
+ CMD_NAMES.append(CMD_NAV_ITEM_NEXT, "CMD_NAV_ITEM_NEXT");
+ CMD_NAMES.append(CMD_NAV_PAN_LEFT, "CMD_NAV_PAN_LEFT");
+ CMD_NAMES.append(CMD_NAV_PAN_RIGHT, "CMD_NAV_PAN_RIGHT");
+ CMD_NAMES.append(CMD_NAV_TOP, "CMD_NAV_TOP");
+ CMD_NAMES.append(CMD_NAV_BOTTOM, "CMD_NAV_BOTTOM");
+ CMD_NAMES.append(CMD_ACTIVATE_CURRENT, "CMD_ACTIVATE_CURRENT");
+ CMD_NAMES.append(CMD_SCROLL_BACKWARD, "CMD_SCROLL_BACKWARD");
+ CMD_NAMES.append(CMD_SCROLL_FORWARD, "CMD_SCROLL_FORWARD");
+ CMD_NAMES.append(CMD_SELECTION_START, "CMD_SELECTION_START");
+ CMD_NAMES.append(CMD_SELECTION_END, "CMD_SELECTION_END");
+ CMD_NAMES.append(CMD_SELECTION_SELECT_ALL, "CMD_SELECTION_SELECT_ALL");
+ CMD_NAMES.append(CMD_SELECTION_CUT, "CMD_SELECTION_CUT");
+ CMD_NAMES.append(CMD_SELECTION_COPY, "CMD_SELECTION_COPY");
+ CMD_NAMES.append(CMD_SELECTION_PASTE, "CMD_SELECTION_PASTE");
+ CMD_NAMES.append(CMD_ROUTE, "CMD_ROUTE");
+ CMD_NAMES.append(CMD_BRAILLE_KEY, "CMD_BRAILLE_KEY");
+ CMD_NAMES.append(CMD_KEY_ENTER, "CMD_KEY_ENTER");
+ CMD_NAMES.append(CMD_KEY_DEL, "CMD_KEY_DEL");
+ CMD_NAMES.append(CMD_KEY_FORWARD_DEL, "CMD_KEY_FORWARD_DEL");
+ CMD_NAMES.append(CMD_GLOBAL_BACK, "CMD_GLOBAL_BACK");
+ CMD_NAMES.append(CMD_GLOBAL_HOME, "CMD_GLOBAL_HOME");
+ CMD_NAMES.append(CMD_GLOBAL_RECENTS, "CMD_GLOBAL_RECENTS");
+ CMD_NAMES.append(CMD_GLOBAL_NOTIFICATIONS, "CMD_GLOBAL_NOTIFICATIONS");
+ CMD_NAMES.append(CMD_HELP, "CMD_HELP");
+ for (int i = 0; i < CMD_NAMES.size(); ++i) {
+ NAMES_TO_CMDS.put(CMD_NAMES.valueAt(i),
+ CMD_NAMES.keyAt(i));
+ }
+ }
+
+ private final int mCommand;
+ private final int mArgument;
+ private final long mEventTime;
+
+ public BrailleInputEvent(int command, int argument, long eventTime) {
+ mCommand = command;
+ mArgument = argument;
+ mEventTime = eventTime;
+ }
+
+ /**
+ * Returns the keyboard command that this event represents.
+ */
+ public int getCommand() {
+ return mCommand;
+ }
+
+ /**
+ * Returns the command-specific argument of the event, or zero if the
+ * command doesn't have an argument. See the individual command constants
+ * for more details.
+ */
+ public int getArgument() {
+ return mArgument;
+ }
+
+ /**
+ * Returns the approximate time when this event happened as
+ * returned by {@link android.os.SystemClock#uptimeMillis}.
+ */
+ public long getEventTime() {
+ return mEventTime;
+ }
+
+ /**
+ * Returns a string representation of {@code command}, or the string
+ * {@code (unknown)} if the command is unknown.
+ */
+ public static String commandToString(int command) {
+ String ret = CMD_NAMES.get(command);
+ return ret != null ? ret : "(unknown)";
+ }
+
+ /**
+ * Returns the command corresponding to {@code commandName}, or
+ * {@link #CMD_NONE} if the name doesn't match any existing command.
+ */
+ public static int stringToCommand(String commandName) {
+ Integer command = NAMES_TO_CMDS.get(commandName);
+ if (command == null) {
+ return CMD_NONE;
+ }
+ return command;
+ }
+
+ /**
+ * Returns the type of argument for the given {@code command}.
+ */
+ public static int argumentType(int command) {
+ switch (command) {
+ case CMD_SELECTION_START:
+ case CMD_SELECTION_END:
+ case CMD_ROUTE:
+ return ARGUMENT_POSITION;
+ case CMD_BRAILLE_KEY:
+ return ARGUMENT_DOTS;
+ default:
+ return ARGUMENT_NONE;
+ }
+ }
+
+ @Override
+ public String toString() {
+ StringBuilder sb = new StringBuilder();
+ sb.append("BrailleInputEvent {");
+ sb.append("amd=");
+ sb.append(commandToString(mCommand));
+ sb.append(", arg=");
+ sb.append(mArgument);
+ sb.append("}");
+ return sb.toString();
+ }
+
+ // For Parcelable support.
+
+ public static final Parcelable.Creator CREATOR =
+ new Parcelable.Creator() {
+ @Override
+ public BrailleInputEvent createFromParcel(Parcel in) {
+ return new BrailleInputEvent(in);
+ }
+
+ @Override
+ public BrailleInputEvent[] newArray(int size) {
+ return new BrailleInputEvent[size];
+ }
+ };
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(Parcel out, int flags) {
+ out.writeInt(mCommand);
+ out.writeInt(mArgument);
+ out.writeLong(mEventTime);
+ }
+
+ private BrailleInputEvent(Parcel in) {
+ mCommand = in.readInt();
+ mArgument = in.readInt();
+ mEventTime = in.readLong();
+ }
+}
diff --git a/src/com/googlecode/eyesfree/braille/display/BrailleKeyBinding.java b/src/com/googlecode/eyesfree/braille/display/BrailleKeyBinding.java
new file mode 100644
index 0000000..92b58d0
--- /dev/null
+++ b/src/com/googlecode/eyesfree/braille/display/BrailleKeyBinding.java
@@ -0,0 +1,100 @@
+/*
+ * Copyright (C) 2012 Google Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+
+package com.googlecode.eyesfree.braille.display;
+
+import android.os.Parcel;
+import android.os.Parcelable;
+
+/**
+ * Represents a binding between a combination of braille device keys and a
+ * command as declared in {@link BrailleInputEvent}.
+ */
+public class BrailleKeyBinding implements Parcelable {
+ private int mCommand;
+ private String[] mKeyNames;
+
+ public BrailleKeyBinding() {
+ }
+
+ public BrailleKeyBinding(int command, String[] keyNames) {
+ mCommand = command;
+ mKeyNames = keyNames;
+ }
+
+ /**
+ * Sets the command for this binding.
+ */
+ public BrailleKeyBinding setCommand(int command) {
+ mCommand = command;
+ return this;
+ }
+
+ /**
+ * Sets the key names for this binding.
+ */
+ public BrailleKeyBinding setKeyNames(String[] keyNames) {
+ mKeyNames = keyNames;
+ return this;
+ }
+
+ /**
+ * Returns the command for this key binding.
+ * @see {@link BrailleInputEvent}.
+ */
+ public int getCommand() {
+ return mCommand;
+ }
+
+ /**
+ * Returns the list of device-specific keys that, when pressed
+ * at the same time, will yield the command of this key binding.
+ */
+ public String[] getKeyNames() {
+ return mKeyNames;
+ }
+
+ // For Parcelable support.
+
+ public static final Parcelable.Creator CREATOR =
+ new Parcelable.Creator() {
+ @Override
+ public BrailleKeyBinding createFromParcel(Parcel in) {
+ return new BrailleKeyBinding(in);
+ }
+
+ @Override
+ public BrailleKeyBinding[] newArray(int size) {
+ return new BrailleKeyBinding[size];
+ }
+ };
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(Parcel out, int flags) {
+ out.writeInt(mCommand);
+ out.writeStringArray(mKeyNames);
+ }
+
+ private BrailleKeyBinding(Parcel in) {
+ mCommand = in.readInt();
+ mKeyNames = in.createStringArray();
+ }
+}
diff --git a/src/com/googlecode/eyesfree/braille/display/Display.java b/src/com/googlecode/eyesfree/braille/display/Display.java
new file mode 100644
index 0000000..54a57a2
--- /dev/null
+++ b/src/com/googlecode/eyesfree/braille/display/Display.java
@@ -0,0 +1,321 @@
+/*
+ * Copyright (C) 2012 Google Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+
+package com.googlecode.eyesfree.braille.display;
+
+import android.os.Message;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.ServiceConnection;
+import android.os.Handler;
+import android.os.IBinder;
+import android.os.RemoteException;
+import android.util.Log;
+
+/**
+ * A client for the braille display service.
+ */
+public class Display {
+ private static final String LOG_TAG = Display.class.getSimpleName();
+ /** Service name used for connecting to the service. */
+ public static final String ACTION_DISPLAY_SERVICE =
+ "com.googlecode.eyesfree.braille.service.ACTION_DISPLAY_SERVICE";
+
+ /** Initial value, which is never reported to the listener. */
+ private static final int STATE_UNKNOWN = -2;
+ public static final int STATE_ERROR = -1;
+ public static final int STATE_NOT_CONNECTED = 0;
+ public static final int STATE_CONNECTED = 1;
+
+ private final OnConnectionStateChangeListener
+ mConnectionStateChangeListener;
+ private final Context mContext;
+ private final DisplayHandler mHandler;
+ private volatile OnInputEventListener mInputEventListener;
+ private static final Intent mServiceIntent =
+ new Intent(ACTION_DISPLAY_SERVICE);
+ private Connection mConnection;
+ private int currentConnectionState = STATE_UNKNOWN;
+ private BrailleDisplayProperties mDisplayProperties;
+ private ServiceCallback mServiceCallback = new ServiceCallback();
+ /**
+ * Delay before the first rebind attempt on bind error or service
+ * disconnect.
+ */
+ private static final int REBIND_DELAY_MILLIS = 500;
+ private static final int MAX_REBIND_ATTEMPTS = 5;
+ private int mNumFailedBinds = 0;
+
+ /**
+ * A callback interface to get informed about connection state changes.
+ */
+ public interface OnConnectionStateChangeListener {
+ void onConnectionStateChanged(int state);
+ }
+
+ /**
+ * A callback interface for input from the braille display.
+ */
+ public interface OnInputEventListener {
+ void onInputEvent(BrailleInputEvent inputEvent);
+ }
+
+ /**
+ * Constructs an instance and connects to the braille display service.
+ * The current thread must have an {@link android.os.Looper} associated
+ * with it. Callbacks from this object will all be executed on the
+ * current thread. Connection state will be reported to {@code listener).
+ */
+ public Display(Context context, OnConnectionStateChangeListener listener) {
+ this(context, listener, null);
+ }
+
+ /**
+ * Constructs an instance and connects to the braille display service.
+ * Callbacks from this object will all be executed on the thread
+ * associated with {@code handler}. If {@code handler} is {@code null},
+ * the current thread must have an {@link android.os.Looper} associated
+ * with it, which will then be used to execute callbacks. Connection
+ * state will be reported to {@code listener).
+ */
+ public Display(Context context, OnConnectionStateChangeListener listener,
+ Handler handler) {
+ mContext = context;
+ mConnectionStateChangeListener = listener;
+ if (handler == null) {
+ mHandler = new DisplayHandler();
+ } else {
+ mHandler = new DisplayHandler(handler);
+ }
+
+ doBindService();
+ }
+
+ /**
+ * Sets a {@code listener} for input events. {@code listener} can be
+ * {@code null} to remove a previously set listener.
+ */
+ public void setOnInputEventListener(OnInputEventListener listener) {
+ mInputEventListener = listener;
+ }
+
+ /**
+ * Returns the display properties, or {@code null} if not connected
+ * to a display.
+ */
+ public BrailleDisplayProperties getDisplayProperties() {
+ return mDisplayProperties;
+ }
+
+ /**
+ * Displays a given dots configuration on the braille display.
+ * @param patterns Dots configuration to be displayed.
+ */
+ public void displayDots(byte[] patterns) {
+ IBrailleService localService = getBrailleService();
+ if (localService != null) {
+ try {
+ localService.displayDots(patterns);
+ } catch (RemoteException ex) {
+ Log.e(LOG_TAG, "Error in displayDots", ex);
+ }
+ } else {
+ Log.v(LOG_TAG, "Error in displayDots: service not connected");
+ }
+ }
+
+ /**
+ * Unbinds from the braille display service and deallocates any
+ * resources. This method should be called when the braille display
+ * is no longer in use by this client.
+ */
+ public void shutdown() {
+ doUnbindService();
+ }
+
+ // NOTE: The methods in this class will be executed in the main
+ // application thread.
+ private class Connection implements ServiceConnection {
+ private volatile IBrailleService mService;
+
+ @Override
+ public void onServiceConnected(ComponentName className,
+ IBinder binder) {
+ Log.i(LOG_TAG, "Connected to braille service");
+ IBrailleService localService =
+ IBrailleService.Stub.asInterface(binder);
+ try {
+ localService.registerCallback(mServiceCallback);
+ mService = localService;
+ synchronized (mHandler) {
+ mNumFailedBinds = 0;
+ }
+ } catch (RemoteException e) {
+ // In this case the service has crashed before we could even do
+ // anything with it.
+ Log.e(LOG_TAG, "Failed to register callback on service", e);
+ // We should get a disconnected call and the rebind
+ // and failure reporting happens in that handler.
+ }
+ }
+
+ @Override
+ public void onServiceDisconnected(ComponentName className) {
+ mService = null;
+ Log.e(LOG_TAG, "Disconnected from braille service");
+ // Report display disconnected for now, this will turn into a
+ // connected state or error state depending on how the retrying
+ // goes.
+ mHandler.reportConnectionState(STATE_NOT_CONNECTED, null);
+ mHandler.scheduleRebind();
+ }
+ }
+
+ // NOTE: The methods of this class will be executed in the IPC
+ // thread pool and not on the main application thread.
+ private class ServiceCallback extends IBrailleServiceCallback.Stub {
+ @Override
+ public void onDisplayConnected(
+ BrailleDisplayProperties displayProperties) {
+ mHandler.reportConnectionState(STATE_CONNECTED, displayProperties);
+ }
+
+ @Override
+ public void onDisplayDisconnected() {
+ mHandler.reportConnectionState(STATE_NOT_CONNECTED, null);
+ }
+
+ @Override
+ public void onInput(BrailleInputEvent inputEvent) {
+ mHandler.reportInputEvent(inputEvent);
+ }
+ }
+
+ private void doBindService() {
+ Connection localConnection = new Connection();
+ if (!mContext.bindService(mServiceIntent, localConnection,
+ Context.BIND_AUTO_CREATE)) {
+ Log.e(LOG_TAG, "Failed to bind Service");
+ mHandler.scheduleRebind();
+ return;
+ }
+ mConnection = localConnection;
+ Log.i(LOG_TAG, "Bound to braille service");
+ }
+
+ private void doUnbindService() {
+ IBrailleService localService = getBrailleService();
+ if (localService != null) {
+ try {
+ localService.unregisterCallback(mServiceCallback);
+ } catch (RemoteException e) {
+ // Nothing to do if the service can't be reached.
+ }
+ }
+ if (mConnection != null) {
+ mContext.unbindService(mConnection);
+ mConnection = null;
+ }
+ }
+
+ private IBrailleService getBrailleService() {
+ Connection localConnection = mConnection;
+ if (localConnection != null) {
+ return localConnection.mService;
+ }
+ return null;
+ }
+
+ private class DisplayHandler extends Handler {
+ private static final int MSG_REPORT_CONNECTION_STATE = 1;
+ private static final int MSG_REPORT_INPUT_EVENT = 2;
+ private static final int MSG_REBIND_SERVICE = 3;
+
+ public DisplayHandler() {
+ }
+
+ public DisplayHandler(Handler handler) {
+ super(handler.getLooper());
+ }
+
+ public void reportConnectionState(final int newState,
+ final BrailleDisplayProperties displayProperties) {
+ obtainMessage(MSG_REPORT_CONNECTION_STATE, newState, 0,
+ displayProperties)
+ .sendToTarget();
+ }
+
+ public void reportInputEvent(BrailleInputEvent event) {
+ obtainMessage(MSG_REPORT_INPUT_EVENT, event).sendToTarget();
+ }
+
+ public void scheduleRebind() {
+ synchronized (this) {
+ if (mNumFailedBinds < MAX_REBIND_ATTEMPTS) {
+ int delay = REBIND_DELAY_MILLIS << mNumFailedBinds;
+ sendEmptyMessageDelayed(MSG_REBIND_SERVICE, delay);
+ ++mNumFailedBinds;
+ Log.w(LOG_TAG, String.format(
+ "Will rebind to braille service in %d ms.", delay));
+ } else {
+ reportConnectionState(STATE_ERROR, null);
+ }
+ }
+ }
+
+ @Override
+ public void handleMessage(Message msg) {
+ switch (msg.what) {
+ case MSG_REPORT_CONNECTION_STATE:
+ handleReportConnectionState(msg.arg1,
+ (BrailleDisplayProperties) msg.obj);
+ break;
+ case MSG_REPORT_INPUT_EVENT:
+ handleReportInputEvent((BrailleInputEvent) msg.obj);
+ break;
+ case MSG_REBIND_SERVICE:
+ handleRebindService();
+ break;
+ }
+ }
+
+ private void handleReportConnectionState(int newState,
+ BrailleDisplayProperties displayProperties) {
+ mDisplayProperties = displayProperties;
+ if (newState != currentConnectionState
+ && mConnectionStateChangeListener != null) {
+ mConnectionStateChangeListener.onConnectionStateChanged(
+ newState);
+ }
+ currentConnectionState = newState;
+ }
+
+ private void handleReportInputEvent(BrailleInputEvent event) {
+ OnInputEventListener localListener = mInputEventListener;
+ if (localListener != null) {
+ localListener.onInputEvent(event);
+ }
+ }
+
+ private void handleRebindService() {
+ if (mConnection != null) {
+ doUnbindService();
+ }
+ doBindService();
+ }
+ }
+}
diff --git a/src/com/googlecode/eyesfree/braille/display/IBrailleService.aidl b/src/com/googlecode/eyesfree/braille/display/IBrailleService.aidl
new file mode 100644
index 0000000..2b478bb
--- /dev/null
+++ b/src/com/googlecode/eyesfree/braille/display/IBrailleService.aidl
@@ -0,0 +1,43 @@
+/*
+ * Copyright (C) 2012 Google Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+
+package com.googlecode.eyesfree.braille.display;
+
+import com.googlecode.eyesfree.braille.display.IBrailleServiceCallback;
+
+/**
+ * Interface for clients to talk to the braille display service.
+ */
+interface IBrailleService {
+ /**
+ * Register a callback for the {@code callingApp} which will receive
+ * certain braille display related events.
+ */
+ boolean registerCallback(in IBrailleServiceCallback callback);
+
+ /**
+ * Unregister a previously registered callback for the {@code callingApp}.
+ */
+ oneway void unregisterCallback(in IBrailleServiceCallback callback);
+
+ /**
+ * Updates the main cells of the connected braille display
+ * with a given dot {@code pattern}.
+ *
+ * @return {@code true} on success and {@code false} otherwise.
+ */
+ void displayDots(in byte[] patterns);
+}
diff --git a/src/com/googlecode/eyesfree/braille/display/IBrailleServiceCallback.aidl b/src/com/googlecode/eyesfree/braille/display/IBrailleServiceCallback.aidl
new file mode 100644
index 0000000..545d1ad
--- /dev/null
+++ b/src/com/googlecode/eyesfree/braille/display/IBrailleServiceCallback.aidl
@@ -0,0 +1,30 @@
+/*
+ * Copyright (C) 2012 Google Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+
+package com.googlecode.eyesfree.braille.display;
+
+import com.googlecode.eyesfree.braille.display.BrailleDisplayProperties;
+import com.googlecode.eyesfree.braille.display.BrailleInputEvent;
+
+/**
+ * Callback interface that a braille display client can expose to
+ * get information about various braille display events.
+ */
+interface IBrailleServiceCallback {
+ void onDisplayConnected(in BrailleDisplayProperties displayProperties);
+ void onDisplayDisconnected();
+ void onInput(in BrailleInputEvent inputEvent);
+}
diff --git a/src/com/googlecode/eyesfree/braille/selfbraille/ISelfBrailleService.aidl b/src/com/googlecode/eyesfree/braille/selfbraille/ISelfBrailleService.aidl
new file mode 100644
index 0000000..770c283
--- /dev/null
+++ b/src/com/googlecode/eyesfree/braille/selfbraille/ISelfBrailleService.aidl
@@ -0,0 +1,28 @@
+/*
+ * Copyright (C) 2012 Google Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+
+package com.googlecode.eyesfree.braille.selfbraille;
+
+import com.googlecode.eyesfree.braille.selfbraille.WriteData;
+
+/**
+ * Interface for a client to control braille output for a part of the
+ * accessibility node tree.
+ */
+interface ISelfBrailleService {
+ void write(IBinder clientToken, in WriteData writeData);
+ oneway void disconnect(IBinder clientToken);
+}
diff --git a/src/com/googlecode/eyesfree/braille/selfbraille/SelfBrailleClient.java b/src/com/googlecode/eyesfree/braille/selfbraille/SelfBrailleClient.java
new file mode 100644
index 0000000..e4a363a
--- /dev/null
+++ b/src/com/googlecode/eyesfree/braille/selfbraille/SelfBrailleClient.java
@@ -0,0 +1,267 @@
+/*
+ * Copyright (C) 2012 Google Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+
+package com.googlecode.eyesfree.braille.selfbraille;
+
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.ServiceConnection;
+import android.content.pm.PackageInfo;
+import android.content.pm.PackageManager;
+import android.content.pm.Signature;
+import android.os.Binder;
+import android.os.Handler;
+import android.os.IBinder;
+import android.os.Message;
+import android.os.RemoteException;
+import android.util.Log;
+
+import java.security.MessageDigest;
+import java.security.NoSuchAlgorithmException;
+
+/**
+ * Client-side interface to the self brailling interface.
+ *
+ * Threading: Instances of this object should be created and shut down
+ * in a thread with a {@link Looper} associated with it. Other methods may
+ * be called on any thread.
+ */
+public class SelfBrailleClient {
+ private static final String LOG_TAG =
+ SelfBrailleClient.class.getSimpleName();
+ private static final String ACTION_SELF_BRAILLE_SERVICE =
+ "com.googlecode.eyesfree.braille.service.ACTION_SELF_BRAILLE_SERVICE";
+ private static final String BRAILLE_BACK_PACKAGE =
+ "com.googlecode.eyesfree.brailleback";
+ private static final Intent mServiceIntent =
+ new Intent(ACTION_SELF_BRAILLE_SERVICE)
+ .setPackage(BRAILLE_BACK_PACKAGE);
+ /**
+ * SHA-1 hash value of the Eyes-Free release key certificate, used to sign
+ * BrailleBack. It was generated from the keystore with:
+ * $ keytool -exportcert -keystore -alias android.keystore \
+ * > cert
+ * $ keytool -printcert -file cert
+ */
+ // The typecasts are to silence a compiler warning about loss of precision
+ private static final byte[] EYES_FREE_CERT_SHA1 = new byte[] {
+ (byte) 0x9B, (byte) 0x42, (byte) 0x4C, (byte) 0x2D,
+ (byte) 0x27, (byte) 0xAD, (byte) 0x51, (byte) 0xA4,
+ (byte) 0x2A, (byte) 0x33, (byte) 0x7E, (byte) 0x0B,
+ (byte) 0xB6, (byte) 0x99, (byte) 0x1C, (byte) 0x76,
+ (byte) 0xEC, (byte) 0xA4, (byte) 0x44, (byte) 0x61
+ };
+ /**
+ * Delay before the first rebind attempt on bind error or service
+ * disconnect.
+ */
+ private static final int REBIND_DELAY_MILLIS = 500;
+ private static final int MAX_REBIND_ATTEMPTS = 5;
+
+ private final Binder mIdentity = new Binder();
+ private final Context mContext;
+ private final boolean mAllowDebugService;
+ private final SelfBrailleHandler mHandler = new SelfBrailleHandler();
+ private boolean mShutdown = false;
+
+ /**
+ * Written in handler thread, read in any thread calling methods on the
+ * object.
+ */
+ private volatile Connection mConnection;
+ /** Protected by synchronizing on mHandler. */
+ private int mNumFailedBinds = 0;
+
+ /**
+ * Constructs an instance of this class. {@code context} is used to bind
+ * to the self braille service. The current thread must have a Looper
+ * associated with it. If {@code allowDebugService} is true, this instance
+ * will connect to a BrailleBack service without requiring it to be signed
+ * by the release key used to sign BrailleBack.
+ */
+ public SelfBrailleClient(Context context, boolean allowDebugService) {
+ mContext = context;
+ mAllowDebugService = allowDebugService;
+ doBindService();
+ }
+
+ /**
+ * Shuts this instance down, deallocating any global resources it is using.
+ * This method must be called on the same thread that created this object.
+ */
+ public void shutdown() {
+ mShutdown = true;
+ doUnbindService();
+ }
+
+ public void write(WriteData writeData) {
+ writeData.validate();
+ ISelfBrailleService localService = getSelfBrailleService();
+ if (localService != null) {
+ try {
+ localService.write(mIdentity, writeData);
+ } catch (RemoteException ex) {
+ Log.e(LOG_TAG, "Self braille write failed", ex);
+ }
+ }
+ }
+
+ private void doBindService() {
+ Connection localConnection = new Connection();
+ if (!mContext.bindService(mServiceIntent, localConnection,
+ Context.BIND_AUTO_CREATE)) {
+ Log.e(LOG_TAG, "Failed to bind to service");
+ mHandler.scheduleRebind();
+ return;
+ }
+ mConnection = localConnection;
+ Log.i(LOG_TAG, "Bound to self braille service");
+ }
+
+ private void doUnbindService() {
+ if (mConnection != null) {
+ ISelfBrailleService localService = getSelfBrailleService();
+ if (localService != null) {
+ try {
+ localService.disconnect(mIdentity);
+ } catch (RemoteException ex) {
+ // Nothing to do.
+ }
+ }
+ mContext.unbindService(mConnection);
+ mConnection = null;
+ }
+ }
+
+ private ISelfBrailleService getSelfBrailleService() {
+ Connection localConnection = mConnection;
+ if (localConnection != null) {
+ return localConnection.mService;
+ }
+ return null;
+ }
+
+ private boolean verifyPackage() {
+ PackageManager pm = mContext.getPackageManager();
+ PackageInfo pi;
+ try {
+ pi = pm.getPackageInfo(BRAILLE_BACK_PACKAGE,
+ PackageManager.GET_SIGNATURES);
+ } catch (PackageManager.NameNotFoundException ex) {
+ Log.w(LOG_TAG, "Can't verify package " + BRAILLE_BACK_PACKAGE,
+ ex);
+ return false;
+ }
+ MessageDigest digest;
+ try {
+ digest = MessageDigest.getInstance("SHA-1");
+ } catch (NoSuchAlgorithmException ex) {
+ Log.e(LOG_TAG, "SHA-1 not supported", ex);
+ return false;
+ }
+ // Check if any of the certificates match our hash.
+ for (Signature signature : pi.signatures) {
+ digest.update(signature.toByteArray());
+ if (MessageDigest.isEqual(EYES_FREE_CERT_SHA1, digest.digest())) {
+ return true;
+ }
+ digest.reset();
+ }
+ if (mAllowDebugService) {
+ Log.w(LOG_TAG, String.format(
+ "*** %s connected to BrailleBack with invalid (debug?) "
+ + "signature ***",
+ mContext.getPackageName()));
+ return true;
+ }
+ return false;
+ }
+ private class Connection implements ServiceConnection {
+ // Read in application threads, written in main thread.
+ private volatile ISelfBrailleService mService;
+
+ @Override
+ public void onServiceConnected(ComponentName className,
+ IBinder binder) {
+ if (!verifyPackage()) {
+ Log.w(LOG_TAG, String.format("Service certificate mismatch "
+ + "for %s, dropping connection",
+ BRAILLE_BACK_PACKAGE));
+ mHandler.unbindService();
+ return;
+ }
+ Log.i(LOG_TAG, "Connected to self braille service");
+ mService = ISelfBrailleService.Stub.asInterface(binder);
+ synchronized (mHandler) {
+ mNumFailedBinds = 0;
+ }
+ }
+
+ @Override
+ public void onServiceDisconnected(ComponentName className) {
+ Log.e(LOG_TAG, "Disconnected from self braille service");
+ mService = null;
+ // Retry by rebinding.
+ mHandler.scheduleRebind();
+ }
+ }
+
+ private class SelfBrailleHandler extends Handler {
+ private static final int MSG_REBIND_SERVICE = 1;
+ private static final int MSG_UNBIND_SERVICE = 2;
+
+ public void scheduleRebind() {
+ synchronized (this) {
+ if (mNumFailedBinds < MAX_REBIND_ATTEMPTS) {
+ int delay = REBIND_DELAY_MILLIS << mNumFailedBinds;
+ sendEmptyMessageDelayed(MSG_REBIND_SERVICE, delay);
+ ++mNumFailedBinds;
+ }
+ }
+ }
+
+ public void unbindService() {
+ sendEmptyMessage(MSG_UNBIND_SERVICE);
+ }
+
+ @Override
+ public void handleMessage(Message msg) {
+ switch (msg.what) {
+ case MSG_REBIND_SERVICE:
+ handleRebindService();
+ break;
+ case MSG_UNBIND_SERVICE:
+ handleUnbindService();
+ break;
+ }
+ }
+
+ private void handleRebindService() {
+ if (mShutdown) {
+ return;
+ }
+ if (mConnection != null) {
+ doUnbindService();
+ }
+ doBindService();
+ }
+
+ private void handleUnbindService() {
+ doUnbindService();
+ }
+ }
+}
diff --git a/src/com/googlecode/eyesfree/braille/selfbraille/WriteData.aidl b/src/com/googlecode/eyesfree/braille/selfbraille/WriteData.aidl
new file mode 100644
index 0000000..b02ec85
--- /dev/null
+++ b/src/com/googlecode/eyesfree/braille/selfbraille/WriteData.aidl
@@ -0,0 +1,19 @@
+/*
+ * Copyright (C) 2012 Google Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+
+package com.googlecode.eyesfree.braille.selfbraille;
+
+parcelable WriteData;
diff --git a/src/com/googlecode/eyesfree/braille/selfbraille/WriteData.java b/src/com/googlecode/eyesfree/braille/selfbraille/WriteData.java
new file mode 100644
index 0000000..3c16502
--- /dev/null
+++ b/src/com/googlecode/eyesfree/braille/selfbraille/WriteData.java
@@ -0,0 +1,185 @@
+/*
+ * Copyright (C) 2012 Google Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+
+package com.googlecode.eyesfree.braille.selfbraille;
+
+import android.os.Bundle;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.view.View;
+import android.view.accessibility.AccessibilityNodeInfo;
+
+/**
+ * Represents what should be shown on the braille display for a
+ * part of the accessibility node tree.
+ */
+public class WriteData implements Parcelable {
+
+ private static final String PROP_SELECTION_START = "selectionStart";
+ private static final String PROP_SELECTION_END = "selectionEnd";
+
+ private AccessibilityNodeInfo mAccessibilityNodeInfo;
+ private CharSequence mText;
+ private Bundle mProperties = Bundle.EMPTY;
+
+ /**
+ * Returns a new {@link WriteData} instance for the given {@code view}.
+ */
+ public static WriteData forView(View view) {
+ AccessibilityNodeInfo node = AccessibilityNodeInfo.obtain(view);
+ WriteData writeData = new WriteData();
+ writeData.mAccessibilityNodeInfo = node;
+ return writeData;
+ }
+
+ public AccessibilityNodeInfo getAccessibilityNodeInfo() {
+ return mAccessibilityNodeInfo;
+ }
+
+ /**
+ * Sets the text to be displayed when the accessibility node associated
+ * with this instance has focus. If this method is not called (or
+ * {@code text} is {@code null}), this client relinquishes control over
+ * this node.
+ */
+ public WriteData setText(CharSequence text) {
+ mText = text;
+ return this;
+ }
+
+ public CharSequence getText() {
+ return mText;
+ }
+
+ /**
+ * Sets the start position in the text of a text selection or cursor that
+ * should be marked on the display. A negative value (the default) means
+ * no selection will be added.
+ */
+ public WriteData setSelectionStart(int v) {
+ writableProperties().putInt(PROP_SELECTION_START, v);
+ return this;
+ }
+
+ /**
+ * @see {@link #setSelectionStart}.
+ */
+ public int getSelectionStart() {
+ return mProperties.getInt(PROP_SELECTION_START, -1);
+ }
+
+ /**
+ * Sets the end of the text selection to be marked on the display. This
+ * value should only be non-negative if the selection start is
+ * non-negative. If this value is <= the selection start, the selection
+ * is a cursor. Otherwise, the selection covers the range from
+ * start(inclusive) to end (exclusive).
+ *
+ * @see {@link android.text.Selection}.
+ */
+ public WriteData setSelectionEnd(int v) {
+ writableProperties().putInt(PROP_SELECTION_END, v);
+ return this;
+ }
+
+ /**
+ * @see {@link #setSelectionEnd}.
+ */
+ public int getSelectionEnd() {
+ return mProperties.getInt(PROP_SELECTION_END, -1);
+ }
+
+ private Bundle writableProperties() {
+ if (mProperties == Bundle.EMPTY) {
+ mProperties = new Bundle();
+ }
+ return mProperties;
+ }
+
+ /**
+ * Checks constraints on the fields that must be satisfied before sending
+ * this instance to the self braille service.
+ * @throws IllegalStateException
+ */
+ public void validate() throws IllegalStateException {
+ if (mAccessibilityNodeInfo == null) {
+ throw new IllegalStateException(
+ "Accessibility node info can't be null");
+ }
+ int selectionStart = getSelectionStart();
+ int selectionEnd = getSelectionEnd();
+ if (mText == null) {
+ if (selectionStart > 0 || selectionEnd > 0) {
+ throw new IllegalStateException(
+ "Selection can't be set without text");
+ }
+ } else {
+ if (selectionStart < 0 && selectionEnd >= 0) {
+ throw new IllegalStateException(
+ "Selection end without start");
+ }
+ int textLength = mText.length();
+ if (selectionStart > textLength || selectionEnd > textLength) {
+ throw new IllegalStateException("Selection out of bounds");
+ }
+ }
+ }
+
+ // For Parcelable support.
+
+ public static final Parcelable.Creator CREATOR =
+ new Parcelable.Creator() {
+ @Override
+ public WriteData createFromParcel(Parcel in) {
+ return new WriteData(in);
+ }
+
+ @Override
+ public WriteData[] newArray(int size) {
+ return new WriteData[size];
+ }
+ };
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ /**
+ * {@inheritDoc}
+ * Note: The {@link AccessibilityNodeInfo} will be
+ * recycled by this method, don't try to use this more than once.
+ */
+ @Override
+ public void writeToParcel(Parcel out, int flags) {
+ mAccessibilityNodeInfo.writeToParcel(out, flags);
+ // The above call recycles the node, so make sure we don't use it
+ // anymore.
+ mAccessibilityNodeInfo = null;
+ out.writeString(mText.toString());
+ out.writeBundle(mProperties);
+ }
+
+ private WriteData() {
+ }
+
+ private WriteData(Parcel in) {
+ mAccessibilityNodeInfo =
+ AccessibilityNodeInfo.CREATOR.createFromParcel(in);
+ mText = in.readString();
+ mProperties = in.readBundle();
+ }
+}
diff --git a/src/com/googlecode/eyesfree/braille/translate/BrailleTranslator.java b/src/com/googlecode/eyesfree/braille/translate/BrailleTranslator.java
new file mode 100644
index 0000000..e7ee9cb
--- /dev/null
+++ b/src/com/googlecode/eyesfree/braille/translate/BrailleTranslator.java
@@ -0,0 +1,35 @@
+/*
+ * Copyright (C) 2012 Google Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+
+package com.googlecode.eyesfree.braille.translate;
+
+/**
+ * Translates from text to braille and the other way according to a
+ * particular translation table.
+ */
+public interface BrailleTranslator {
+ /**
+ * Translates a string into the corresponding dot patterns and returns the
+ * resulting byte array. Returns {@code null} on error.
+ */
+ byte[] translate(String text);
+
+ /**
+ * Translates the braille {@code cells} into the corresponding text, which
+ * is returned. Returns {@code null} on error.
+ */
+ String backTranslate(byte[] cells);
+}
diff --git a/src/com/googlecode/eyesfree/braille/translate/ITranslatorService.aidl b/src/com/googlecode/eyesfree/braille/translate/ITranslatorService.aidl
new file mode 100644
index 0000000..1ccab87
--- /dev/null
+++ b/src/com/googlecode/eyesfree/braille/translate/ITranslatorService.aidl
@@ -0,0 +1,47 @@
+/*
+ * Copyright (C) 2012 Google Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+
+package com.googlecode.eyesfree.braille.translate;
+
+import com.googlecode.eyesfree.braille.translate.ITranslatorServiceCallback;
+
+interface ITranslatorService {
+ /**
+ * Sets a callback to be called when the service is ready to translate.
+ * Using any of the other methods in this interface before the
+ * callback is called with a successful status will return
+ * failure.
+ */
+ void setCallback(ITranslatorServiceCallback callback);
+
+ /**
+ * Makes sure that the given table string is valid and that the
+ * table compiles.
+ */
+ boolean checkTable(String tableName);
+
+ /**
+ * Translates text into braille according to the give tableName.
+ * Returns null on fatal translation errors.
+ */
+ byte[] translate(String text, String tableName);
+
+ /**
+ * Translates braille cells into text according to the given table
+ * name. Returns null on fatal translation errors.
+ */
+ String backTranslate(in byte[] cells, String tableName);
+}
diff --git a/src/com/googlecode/eyesfree/braille/translate/ITranslatorServiceCallback.aidl b/src/com/googlecode/eyesfree/braille/translate/ITranslatorServiceCallback.aidl
new file mode 100644
index 0000000..91c74cb
--- /dev/null
+++ b/src/com/googlecode/eyesfree/braille/translate/ITranslatorServiceCallback.aidl
@@ -0,0 +1,21 @@
+/*
+ * Copyright (C) 2012 Google Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+
+package com.googlecode.eyesfree.braille.translate;
+
+oneway interface ITranslatorServiceCallback {
+ void onInit(int status);
+}
diff --git a/src/com/googlecode/eyesfree/braille/translate/TranslatorManager.java b/src/com/googlecode/eyesfree/braille/translate/TranslatorManager.java
new file mode 100644
index 0000000..841a041
--- /dev/null
+++ b/src/com/googlecode/eyesfree/braille/translate/TranslatorManager.java
@@ -0,0 +1,278 @@
+/*
+ * Copyright (C) 2012 Google Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+
+package com.googlecode.eyesfree.braille.translate;
+
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.ServiceConnection;
+import android.os.Handler;
+import android.os.IBinder;
+import android.os.Message;
+import android.os.RemoteException;
+import android.util.Log;
+
+/**
+ * Client-side interface to the central braille translator service.
+ *
+ * This class can be used to retrieve {@link BrailleTranslator} instances for
+ * performing translation between text and braille cells.
+ *
+ * Typically, an instance of this class is created at application
+ * initialization time and destroyed using the {@link destroy()} method when
+ * the application is about to be destroyed. It is recommended that the
+ * instance is destroyed and recreated if braille translation is not going to
+ * be need for a long period of time.
+ *
+ * Threading:
+ * The object must be destroyed on the same thread it was created.
+ * Other methods may be called from any thread.
+ */
+public class TranslatorManager {
+ private static final String LOG_TAG =
+ TranslatorManager.class.getSimpleName();
+ private static final String ACTION_TRANSLATOR_SERVICE =
+ "com.googlecode.eyesfree.braille.service.ACTION_TRANSLATOR_SERVICE";
+ private static final Intent mServiceIntent =
+ new Intent(ACTION_TRANSLATOR_SERVICE);
+ /**
+ * Delay before the first rebind attempt on bind error or service
+ * disconnect.
+ */
+ private static final int REBIND_DELAY_MILLIS = 500;
+ private static final int MAX_REBIND_ATTEMPTS = 5;
+ public static final int ERROR = -1;
+ public static final int SUCCESS = 0;
+
+ /**
+ * A callback interface to get notified when the translation
+ * manager is ready to be used, or an error occurred during
+ * initialization.
+ */
+ public interface OnInitListener {
+ /**
+ * Called exactly once when it has been determined that the
+ * translation service is either ready to be used ({@code SUCCESS})
+ * or the service is not available {@code ERROR}.
+ */
+ public void onInit(int status);
+ }
+
+ private final Context mContext;
+ private final TranslatorManagerHandler mHandler =
+ new TranslatorManagerHandler();
+ private final ServiceCallback mServiceCallback = new ServiceCallback();
+
+ private OnInitListener mOnInitListener;
+ private Connection mConnection;
+ private int mNumFailedBinds = 0;
+
+ /**
+ * Constructs an instance. {@code context} is used to bind to the
+ * translator service. The other methods of this class should not be
+ * called (they will fail) until {@code onInitListener.onInit()}
+ * is called.
+ */
+ public TranslatorManager(Context context, OnInitListener onInitListener) {
+ mContext = context;
+ mOnInitListener = onInitListener;
+ doBindService();
+ }
+
+ /**
+ * Destroys this instance, deallocating any global resources it is using.
+ * Any {@link BrailleTranslator} objects that were created using this
+ * object are invalid after this call.
+ */
+ public void destroy() {
+ doUnbindService();
+ mHandler.destroy();
+ }
+
+ /**
+ * Returns a new {@link BrailleTranslator} for the translation
+ * table specified by {@code tableName}.
+ */
+ // TODO: Document how to discover valid table names.
+ public BrailleTranslator getTranslator(String tableName) {
+ ITranslatorService localService = getTranslatorService();
+ if (localService != null) {
+ try {
+ if (localService.checkTable(tableName)) {
+ return new BrailleTranslatorImpl(tableName);
+ }
+ } catch (RemoteException ex) {
+ Log.e(LOG_TAG, "Error in getTranslator", ex);
+ }
+ }
+ return null;
+ }
+
+ private void doBindService() {
+ Connection localConnection = new Connection();
+ if (!mContext.bindService(mServiceIntent, localConnection,
+ Context.BIND_AUTO_CREATE)) {
+ Log.e(LOG_TAG, "Failed to bind to service");
+ mHandler.scheduleRebind();
+ return;
+ }
+ mConnection = localConnection;
+ Log.i(LOG_TAG, "Bound to translator service");
+ }
+
+ private void doUnbindService() {
+ if (mConnection != null) {
+ mContext.unbindService(mConnection);
+ mConnection = null;
+ }
+ }
+
+ private ITranslatorService getTranslatorService() {
+ Connection localConnection = mConnection;
+ if (localConnection != null) {
+ return localConnection.mService;
+ }
+ return null;
+ }
+
+ private class Connection implements ServiceConnection {
+ // Read in application threads, written in main thread.
+ private volatile ITranslatorService mService;
+
+ @Override
+ public void onServiceConnected(ComponentName className,
+ IBinder binder) {
+ Log.i(LOG_TAG, "Connected to translation service");
+ ITranslatorService localService =
+ ITranslatorService.Stub.asInterface(binder);
+ try {
+ localService.setCallback(mServiceCallback);
+ mService = localService;
+ synchronized (mHandler) {
+ mNumFailedBinds = 0;
+ }
+ } catch (RemoteException ex) {
+ // Service went away, rely on disconnect handler to
+ // schedule a rebind.
+ Log.e(LOG_TAG, "Error when setting callback", ex);
+ }
+ }
+
+ @Override
+ public void onServiceDisconnected(ComponentName className) {
+ Log.e(LOG_TAG, "Disconnected from translator service");
+ mService = null;
+ // Retry by rebinding, and finally call the onInit if aplicable.
+ mHandler.scheduleRebind();
+ }
+ }
+
+ private class BrailleTranslatorImpl implements BrailleTranslator {
+ private final String mTable;
+
+ public BrailleTranslatorImpl(String table) {
+ mTable = table;
+ }
+
+ @Override
+ public byte[] translate(String text) {
+ ITranslatorService localService = getTranslatorService();
+ if (localService != null) {
+ try {
+ return localService.translate(text, mTable);
+ } catch (RemoteException ex) {
+ Log.e(LOG_TAG, "Error in translate", ex);
+ }
+ }
+ return null;
+ }
+
+ @Override
+ public String backTranslate(byte[] cells) {
+ ITranslatorService localService = getTranslatorService();
+ if (localService != null) {
+ try {
+ return localService.backTranslate(cells, mTable);
+ } catch (RemoteException ex) {
+ Log.e(LOG_TAG, "Error in backTranslate", ex);
+ }
+ }
+ return null;
+ }
+ }
+
+ private class ServiceCallback extends ITranslatorServiceCallback.Stub {
+ @Override
+ public void onInit(int status) {
+ mHandler.onInit(status);
+ }
+ }
+
+ private class TranslatorManagerHandler extends Handler {
+ private static final int MSG_ON_INIT = 1;
+ private static final int MSG_REBIND_SERVICE = 2;
+
+ public void onInit(int status) {
+ obtainMessage(MSG_ON_INIT, status, 0).sendToTarget();
+ }
+
+ public void destroy() {
+ mOnInitListener = null;
+ // Cacnel outstanding messages, most importantly
+ // scheduled rebinds.
+ removeCallbacksAndMessages(null);
+ }
+
+ public void scheduleRebind() {
+ synchronized (this) {
+ if (mNumFailedBinds < MAX_REBIND_ATTEMPTS) {
+ int delay = REBIND_DELAY_MILLIS << mNumFailedBinds;
+ sendEmptyMessageDelayed(MSG_REBIND_SERVICE, delay);
+ ++mNumFailedBinds;
+ } else {
+ onInit(ERROR);
+ }
+ }
+ }
+
+ @Override
+ public void handleMessage(Message msg) {
+ switch (msg.what) {
+ case MSG_ON_INIT:
+ handleOnInit(msg.arg1);
+ break;
+ case MSG_REBIND_SERVICE:
+ handleRebindService();
+ break;
+ }
+ }
+
+ private void handleOnInit(int status) {
+ if (mOnInitListener != null) {
+ mOnInitListener.onInit(status);
+ mOnInitListener = null;
+ }
+ }
+
+ private void handleRebindService() {
+ if (mConnection != null) {
+ doUnbindService();
+ }
+ doBindService();
+ }
+ }
+}
diff --git a/src/org/chromium/android_webview/AndroidProtocolHandler.java b/src/org/chromium/android_webview/AndroidProtocolHandler.java
new file mode 100644
index 0000000..4c7ca07
--- /dev/null
+++ b/src/org/chromium/android_webview/AndroidProtocolHandler.java
@@ -0,0 +1,235 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+package org.chromium.android_webview;
+
+import android.content.Context;
+import android.content.res.AssetManager;
+import android.net.Uri;
+import android.util.Log;
+import android.util.TypedValue;
+
+import java.io.InputStream;
+import java.io.IOException;
+import java.net.URLConnection;
+import java.util.List;
+
+import org.chromium.base.CalledByNativeUnchecked;
+import org.chromium.base.JNINamespace;
+
+/**
+ * Implements the Java side of Android URL protocol jobs.
+ * See android_protocol_handler.cc.
+ */
+@JNINamespace("android_webview")
+public class AndroidProtocolHandler {
+ private static final String TAG = "AndroidProtocolHandler";
+
+ // Supported URL schemes. This needs to be kept in sync with
+ // clank/native/framework/chrome/url_request_android_job.cc.
+ private static final String FILE_SCHEME = "file";
+ private static final String CONTENT_SCHEME = "content";
+
+ /**
+ * Open an InputStream for an Android resource.
+ * @param context The context manager.
+ * @param url The url to load.
+ * @return An InputStream to the Android resource.
+ */
+ // TODO(bulach): this should have either a throw clause, or
+ // handle the exception in the java side rather than the native side.
+ @CalledByNativeUnchecked
+ public static InputStream open(Context context, String url) {
+ Uri uri = verifyUrl(url);
+ if (uri == null) {
+ return null;
+ }
+ String path = uri.getPath();
+ if (uri.getScheme().equals(FILE_SCHEME)) {
+ if (path.startsWith(nativeGetAndroidAssetPath())) {
+ return openAsset(context, uri);
+ } else if (path.startsWith(nativeGetAndroidResourcePath())) {
+ return openResource(context, uri);
+ }
+ } else if (uri.getScheme().equals(CONTENT_SCHEME)) {
+ return openContent(context, uri);
+ }
+ return null;
+ }
+
+ private static int getFieldId(Context context, String assetType, String assetName)
+ throws ClassNotFoundException, NoSuchFieldException, IllegalAccessException {
+ Class> d = context.getClassLoader()
+ .loadClass(context.getPackageName() + ".R$" + assetType);
+ java.lang.reflect.Field field = d.getField(assetName);
+ int id = field.getInt(null);
+ return id;
+ }
+
+ private static int getValueType(Context context, int field_id) {
+ TypedValue value = new TypedValue();
+ context.getResources().getValue(field_id, value, true);
+ return value.type;
+ }
+
+ private static InputStream openResource(Context context, Uri uri) {
+ assert(uri.getScheme().equals(FILE_SCHEME));
+ assert(uri.getPath() != null);
+ assert(uri.getPath().startsWith(nativeGetAndroidResourcePath()));
+ // The path must be of the form "/android_res/asset_type/asset_name.ext".
+ List pathSegments = uri.getPathSegments();
+ if (pathSegments.size() != 3) {
+ Log.e(TAG, "Incorrect resource path: " + uri);
+ return null;
+ }
+ String assetPath = pathSegments.get(0);
+ String assetType = pathSegments.get(1);
+ String assetName = pathSegments.get(2);
+ if (!("/" + assetPath + "/").equals(nativeGetAndroidResourcePath())) {
+ Log.e(TAG, "Resource path does not start with " + nativeGetAndroidResourcePath() +
+ ": " + uri);
+ return null;
+ }
+ // Drop the file extension.
+ assetName = assetName.split("\\.")[0];
+ try {
+ // Use the application context for resolving the resource package name so that we do
+ // not use the browser's own resources. Note that if 'context' here belongs to the
+ // test suite, it does not have a separate application context. In that case we use
+ // the original context object directly.
+ if (context.getApplicationContext() != null) {
+ context = context.getApplicationContext();
+ }
+ int field_id = getFieldId(context, assetType, assetName);
+ int value_type = getValueType(context, field_id);
+ if (value_type == TypedValue.TYPE_STRING) {
+ return context.getResources().openRawResource(field_id);
+ } else {
+ Log.e(TAG, "Asset not of type string: " + uri);
+ return null;
+ }
+ } catch (ClassNotFoundException e) {
+ Log.e(TAG, "Unable to open resource URL: " + uri, e);
+ return null;
+ } catch (NoSuchFieldException e) {
+ Log.e(TAG, "Unable to open resource URL: " + uri, e);
+ return null;
+ } catch (IllegalAccessException e) {
+ Log.e(TAG, "Unable to open resource URL: " + uri, e);
+ return null;
+ }
+ }
+
+ private static InputStream openAsset(Context context, Uri uri) {
+ assert(uri.getScheme().equals(FILE_SCHEME));
+ assert(uri.getPath() != null);
+ assert(uri.getPath().startsWith(nativeGetAndroidAssetPath()));
+ String path = uri.getPath().replaceFirst(nativeGetAndroidAssetPath(), "");
+ try {
+ AssetManager assets = context.getAssets();
+ return assets.open(path, AssetManager.ACCESS_STREAMING);
+ } catch (IOException e) {
+ Log.e(TAG, "Unable to open asset URL: " + uri);
+ return null;
+ }
+ }
+
+ private static InputStream openContent(Context context, Uri uri) {
+ assert(uri.getScheme().equals(CONTENT_SCHEME));
+ try {
+ // We strip the query parameters before opening the stream to
+ // ensure that the URL we try to load exactly matches the URL
+ // we have permission to read.
+ Uri baseUri = stripQueryParameters(uri);
+ return context.getContentResolver().openInputStream(baseUri);
+ } catch (Exception e) {
+ Log.e(TAG, "Unable to open content URL: " + uri);
+ return null;
+ }
+ }
+
+ /**
+ * Determine the mime type for an Android resource.
+ * @param context The context manager.
+ * @param stream The opened input stream which to examine.
+ * @param url The url from which the stream was opened.
+ * @return The mime type or null if the type is unknown.
+ */
+ // TODO(bulach): this should have either a throw clause, or
+ // handle the exception in the java side rather than the native side.
+ @CalledByNativeUnchecked
+ public static String getMimeType(Context context, InputStream stream, String url) {
+ Uri uri = verifyUrl(url);
+ if (uri == null) {
+ return null;
+ }
+ String path = uri.getPath();
+ // The content URL type can be queried directly.
+ if (uri.getScheme().equals(CONTENT_SCHEME)) {
+ return context.getContentResolver().getType(uri);
+ // Asset files may have a known extension.
+ } else if (uri.getScheme().equals(FILE_SCHEME) &&
+ path.startsWith(nativeGetAndroidAssetPath())) {
+ String mimeType = URLConnection.guessContentTypeFromName(path);
+ if (mimeType != null) {
+ return mimeType;
+ }
+ }
+ // Fall back to sniffing the type from the stream.
+ try {
+ return URLConnection.guessContentTypeFromStream(stream);
+ } catch (IOException e) {
+ return null;
+ }
+ }
+
+ /**
+ * Make sure the given string URL is correctly formed and parse it into a Uri.
+ * @return a Uri instance, or null if the URL was invalid.
+ */
+ private static Uri verifyUrl(String url) {
+ if (url == null) {
+ return null;
+ }
+ Uri uri = Uri.parse(url);
+ if (uri == null) {
+ Log.e(TAG, "Malformed URL: " + url);
+ return null;
+ }
+ String path = uri.getPath();
+ if (path == null || path.length() == 0) {
+ Log.e(TAG, "URL does not have a path: " + url);
+ return null;
+ }
+ return uri;
+ }
+
+ /**
+ * Remove query parameters from a Uri.
+ * @param uri The input uri.
+ * @return The given uri without query parameters.
+ */
+ private static Uri stripQueryParameters(Uri uri) {
+ assert(uri.getAuthority() != null);
+ assert(uri.getPath() != null);
+ Uri.Builder builder = new Uri.Builder();
+ builder.scheme(uri.getScheme());
+ builder.encodedAuthority(uri.getAuthority());
+ builder.encodedPath(uri.getPath());
+ return builder.build();
+ }
+
+ /**
+ * Set the context to be used for resolving resource queries.
+ * @param context Context to be used, or null for the default application
+ * context.
+ */
+ public static void setResourceContextForTesting(Context context) {
+ nativeSetResourceContextForTesting(context);
+ }
+
+ private static native void nativeSetResourceContextForTesting(Context context);
+ private static native String nativeGetAndroidAssetPath();
+ private static native String nativeGetAndroidResourcePath();
+}
diff --git a/src/org/chromium/android_webview/AwBrowserContext.java b/src/org/chromium/android_webview/AwBrowserContext.java
new file mode 100644
index 0000000..2536d7b
--- /dev/null
+++ b/src/org/chromium/android_webview/AwBrowserContext.java
@@ -0,0 +1,66 @@
+// Copyright (c) 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+package org.chromium.android_webview;
+
+import android.content.SharedPreferences;
+
+import org.chromium.content.browser.ContentViewStatics;
+
+/**
+ * Java side of the Browser Context: contains all the java side objects needed to host one
+ * browing session (i.e. profile).
+ * Note that due to running in single process mode, and limitations on renderer process only
+ * being able to use a single browser context, currently there can only be one AwBrowserContext
+ * instance, so at this point the class mostly exists for conceptual clarity.
+ *
+ * Obtain the default (singleton) instance with AwBrowserProcess.getDefaultBrowserContext().
+ */
+public class AwBrowserContext {
+
+ private SharedPreferences mSharedPreferences;
+
+ private AwGeolocationPermissions mGeolocationPermissions;
+ private AwCookieManager mCookieManager;
+ private AwFormDatabase mFormDatabase;
+
+ public AwBrowserContext(SharedPreferences sharedPreferences) {
+ mSharedPreferences = sharedPreferences;
+ }
+
+ public AwGeolocationPermissions getGeolocationPermissions() {
+ if (mGeolocationPermissions == null) {
+ mGeolocationPermissions = new AwGeolocationPermissions(mSharedPreferences);
+ }
+ return mGeolocationPermissions;
+ }
+
+ public AwCookieManager getCookieManager() {
+ if (mCookieManager == null) {
+ mCookieManager = new AwCookieManager();
+ }
+ return mCookieManager;
+ }
+
+ public AwFormDatabase getFormDatabase() {
+ if (mFormDatabase == null) {
+ mFormDatabase = new AwFormDatabase();
+ }
+ return mFormDatabase;
+ }
+
+ /**
+ * @see android.webkit.WebView#pauseTimers()
+ */
+ public void pauseTimers() {
+ ContentViewStatics.setWebKitSharedTimersSuspended(true);
+ }
+
+ /**
+ * @see android.webkit.WebView#resumeTimers()
+ */
+ public void resumeTimers() {
+ ContentViewStatics.setWebKitSharedTimersSuspended(false);
+ }
+}
\ No newline at end of file
diff --git a/src/org/chromium/android_webview/AwBrowserProcess.java b/src/org/chromium/android_webview/AwBrowserProcess.java
new file mode 100644
index 0000000..7c3ab76
--- /dev/null
+++ b/src/org/chromium/android_webview/AwBrowserProcess.java
@@ -0,0 +1,59 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+package org.chromium.android_webview;
+
+import android.content.Context;
+import android.content.SharedPreferences;
+
+import org.chromium.base.PathUtils;
+import org.chromium.base.ThreadUtils;
+import org.chromium.content.app.LibraryLoader;
+import org.chromium.content.browser.AndroidBrowserProcess;
+import org.chromium.content.common.ProcessInitException;
+
+/**
+ * Wrapper for the steps needed to initialize the java and native sides of webview chromium.
+ */
+public abstract class AwBrowserProcess {
+ private static final String PRIVATE_DATA_DIRECTORY_SUFFIX = "webview";
+
+ /**
+ * Loads the native library, and performs basic static construction of objects needed
+ * to run webview in this process. Does not create threads; safe to call from zygote.
+ * Note: it is up to the caller to ensure this is only called once.
+ */
+ public static void loadLibrary() {
+ PathUtils.setPrivateDataDirectorySuffix(PRIVATE_DATA_DIRECTORY_SUFFIX);
+ try {
+ LibraryLoader.loadNow();
+ } catch (ProcessInitException e) {
+ throw new RuntimeException("Cannot load WebView", e);
+ }
+ }
+
+ /**
+ * Starts the chromium browser process running within this process. Creates threads
+ * and performs other per-app resource allocations; must not be called from zygote.
+ * Note: it is up to the caller to ensure this is only called once.
+ * @param context The Android application context
+ */
+ public static void start(final Context context) {
+ // We must post to the UI thread to cover the case that the user
+ // has invoked Chromium startup by using the (thread-safe)
+ // CookieManager rather than creating a WebView.
+ ThreadUtils.runOnUiThreadBlocking(new Runnable() {
+ @Override
+ public void run() {
+ try {
+ LibraryLoader.ensureInitialized();
+ AndroidBrowserProcess.init(context,
+ AndroidBrowserProcess.MAX_RENDERERS_SINGLE_PROCESS);
+ } catch (ProcessInitException e) {
+ throw new RuntimeException("Cannot initialize WebView", e);
+ }
+ }
+ });
+ }
+}
diff --git a/src/org/chromium/android_webview/AwContentVideoViewDelegate.java b/src/org/chromium/android_webview/AwContentVideoViewDelegate.java
new file mode 100644
index 0000000..6a82e62
--- /dev/null
+++ b/src/org/chromium/android_webview/AwContentVideoViewDelegate.java
@@ -0,0 +1,52 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+package org.chromium.android_webview;
+
+import android.content.Context;
+import android.content.pm.ActivityInfo;
+import android.view.View;
+import android.webkit.WebChromeClient.CustomViewCallback;
+
+import org.chromium.content.browser.ContentVideoViewContextDelegate;
+
+/**
+ * This further delegates the responsibility displaying full-screen video to the
+ * Webview client.
+ */
+public class AwContentVideoViewDelegate implements ContentVideoViewContextDelegate {
+ private AwContentsClient mAwContentsClient;
+ private Context mContext;
+
+ public AwContentVideoViewDelegate(AwContentsClient client, Context context) {
+ mAwContentsClient = client;
+ mContext = context;
+ }
+
+ @Override
+ public void onShowCustomView(View view) {
+ CustomViewCallback cb = new CustomViewCallback() {
+ @Override
+ public void onCustomViewHidden() {
+ // TODO: we need to invoke ContentVideoView.onDestroyContentVideoView() here.
+ }
+ };
+ mAwContentsClient.onShowCustomView(view, cb);
+ }
+
+ @Override
+ public void onDestroyContentVideoView() {
+ mAwContentsClient.onHideCustomView();
+ }
+
+ @Override
+ public Context getContext() {
+ return mContext;
+ }
+
+ @Override
+ public View getVideoLoadingProgressView() {
+ return mAwContentsClient.getVideoLoadingProgressView();
+ }
+}
diff --git a/src/org/chromium/android_webview/AwContents.java b/src/org/chromium/android_webview/AwContents.java
new file mode 100644
index 0000000..499a42c
--- /dev/null
+++ b/src/org/chromium/android_webview/AwContents.java
@@ -0,0 +1,1494 @@
+// Copyright 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+package org.chromium.android_webview;
+
+import android.annotation.SuppressLint;
+import android.content.res.Configuration;
+import android.graphics.Bitmap;
+import android.graphics.Canvas;
+import android.graphics.Color;
+import android.graphics.Picture;
+import android.graphics.Rect;
+import android.net.http.SslCertificate;
+import android.os.AsyncTask;
+import android.os.Build;
+import android.os.Bundle;
+import android.os.Message;
+import android.text.TextUtils;
+import android.util.Log;
+import android.view.KeyEvent;
+import android.view.MotionEvent;
+import android.view.View;
+import android.view.ViewGroup;
+import android.view.ViewTreeObserver;
+import android.view.accessibility.AccessibilityEvent;
+import android.view.accessibility.AccessibilityNodeInfo;
+import android.view.inputmethod.EditorInfo;
+import android.view.inputmethod.InputConnection;
+import android.webkit.GeolocationPermissions;
+import android.webkit.ValueCallback;
+import com.google.common.annotations.VisibleForTesting;
+import org.chromium.base.CalledByNative;
+import org.chromium.base.JNINamespace;
+import org.chromium.base.ThreadUtils;
+import org.chromium.content.browser.ContentSettings;
+import org.chromium.content.browser.ContentVideoView;
+import org.chromium.content.browser.ContentViewClient;
+import org.chromium.content.browser.ContentViewCore;
+import org.chromium.content.browser.ContentViewStatics;
+import org.chromium.content.browser.LoadUrlParams;
+import org.chromium.content.browser.NavigationHistory;
+import org.chromium.content.browser.PageTransitionTypes;
+import org.chromium.content.common.CleanupReference;
+import org.chromium.components.navigation_interception.InterceptNavigationDelegate;
+import org.chromium.components.navigation_interception.NavigationParams;
+import org.chromium.net.GURLUtils;
+import org.chromium.ui.gfx.DeviceDisplayInfo;
+import java.io.File;
+import java.lang.annotation.Annotation;
+import java.net.MalformedURLException;
+import java.net.URL;
+
+/**
+ * Exposes the native AwContents class, and together these classes wrap the ContentViewCore
+ * and Browser components that are required to implement Android WebView API. This is the
+ * primary entry point for the WebViewProvider implementation; it holds a 1:1 object
+ * relationship with application WebView instances.
+ * (We define this class independent of the hidden WebViewProvider interfaces, to allow
+ * continuous build & test in the open source SDK-based tree).
+ */
+@JNINamespace("android_webview")
+public class AwContents {
+ private static final String TAG = AwContents.class.getSimpleName();
+
+ private static final String WEB_ARCHIVE_EXTENSION = ".mht";
+
+ /**
+ * WebKit hit test related data strcutre. These are used to implement
+ * getHitTestResult, requestFocusNodeHref, requestImageRef methods in WebView.
+ * All values should be updated together. The native counterpart is
+ * AwHitTestData.
+ */
+ public static class HitTestData {
+ // Used in getHitTestResult.
+ public int hitTestResultType;
+ public String hitTestResultExtraData;
+
+ // Used in requestFocusNodeHref (all three) and requestImageRef (only imgSrc).
+ public String href;
+ public String anchorText;
+ public String imgSrc;
+ }
+
+ /**
+ * Interface that consumers of {@link AwContents} must implement to allow the proper
+ * dispatching of view methods through the containing view.
+ */
+ public interface InternalAccessDelegate extends ContentViewCore.InternalAccessDelegate {
+ /**
+ * @see View#setMeasuredDimension(int, int)
+ */
+ void setMeasuredDimension(int measuredWidth, int measuredHeight);
+
+ /**
+ * Requests a callback on the native DrawGL method (see getAwDrawGLFunction)
+ * if called from within onDraw, |canvas| will be non-null and hardware accelerated.
+ * otherwise, |canvas| will be null, and the container view itself will be hardware
+ * accelerated.
+ *
+ * @return false indicates the GL draw request was not accepted, and the caller
+ * should fallback to the SW path.
+ */
+ boolean requestDrawGL(Canvas canvas);
+ }
+
+ private int mNativeAwContents;
+ private AwBrowserContext mBrowserContext;
+ private ViewGroup mContainerView;
+ private ContentViewCore mContentViewCore;
+ private AwContentsClient mContentsClient;
+ private AwContentsClientBridge mContentsClientBridge;
+ private AwWebContentsDelegate mWebContentsDelegate;
+ private AwContentsIoThreadClient mIoThreadClient;
+ private InterceptNavigationDelegateImpl mInterceptNavigationDelegate;
+ private InternalAccessDelegate mInternalAccessAdapter;
+ private final AwLayoutSizer mLayoutSizer;
+ private AwZoomControls mZoomControls;
+ // This can be accessed on any thread after construction. See AwContentsIoThreadClient.
+ private final AwSettings mSettings;
+ private boolean mIsPaused;
+ private Bitmap mFavicon;
+ private boolean mHasRequestedVisitedHistoryFromClient;
+ // TODO(boliu): This should be in a global context, not per webview.
+ private final double mDIPScale;
+
+ // Must call nativeUpdateLastHitTestData first to update this before use.
+ private final HitTestData mPossiblyStaleHitTestData;
+
+ private DefaultVideoPosterRequestHandler mDefaultVideoPosterRequestHandler;
+
+ private boolean mNewPictureInvalidationOnly;
+
+ private Rect mGlobalVisibleBounds;
+ private int mLastGlobalVisibleWidth;
+ private int mLastGlobalVisibleHeight;
+
+ private boolean mContainerViewFocused;
+ private boolean mWindowFocused;
+
+ private static final class DestroyRunnable implements Runnable {
+ private int mNativeAwContents;
+ private DestroyRunnable(int nativeAwContents) {
+ mNativeAwContents = nativeAwContents;
+ }
+ @Override
+ public void run() {
+ nativeDestroy(mNativeAwContents);
+ }
+ }
+
+ private CleanupReference mCleanupReference;
+
+ //--------------------------------------------------------------------------------------------
+ private class IoThreadClientImpl implements AwContentsIoThreadClient {
+ // All methods are called on the IO thread.
+
+ @Override
+ public int getCacheMode() {
+ return mSettings.getCacheMode();
+ }
+
+ @Override
+ public InterceptedRequestData shouldInterceptRequest(final String url,
+ boolean isMainFrame) {
+ InterceptedRequestData interceptedRequestData;
+ // Return the response directly if the url is default video poster url.
+ interceptedRequestData = mDefaultVideoPosterRequestHandler.shouldInterceptRequest(url);
+ if (interceptedRequestData != null) return interceptedRequestData;
+
+ interceptedRequestData = mContentsClient.shouldInterceptRequest(url);
+
+ if (interceptedRequestData == null) {
+ mContentsClient.getCallbackHelper().postOnLoadResource(url);
+ }
+
+ if (isMainFrame && interceptedRequestData != null &&
+ interceptedRequestData.getData() == null) {
+ // In this case the intercepted URLRequest job will simulate an empty response
+ // which doesn't trigger the onReceivedError callback. For WebViewClassic
+ // compatibility we synthesize that callback. http://crbug.com/180950
+ mContentsClient.getCallbackHelper().postOnReceivedError(
+ ErrorCodeConversionHelper.ERROR_UNKNOWN,
+ null /* filled in by the glue layer */, url);
+ }
+ return interceptedRequestData;
+ }
+
+ @Override
+ public boolean shouldBlockContentUrls() {
+ return !mSettings.getAllowContentAccess();
+ }
+
+ @Override
+ public boolean shouldBlockFileUrls() {
+ return !mSettings.getAllowFileAccess();
+ }
+
+ @Override
+ public boolean shouldBlockNetworkLoads() {
+ return mSettings.getBlockNetworkLoads();
+ }
+
+ @Override
+ public void onDownloadStart(String url,
+ String userAgent,
+ String contentDisposition,
+ String mimeType,
+ long contentLength) {
+ mContentsClient.getCallbackHelper().postOnDownloadStart(url, userAgent,
+ contentDisposition, mimeType, contentLength);
+ }
+
+ @Override
+ public void newLoginRequest(String realm, String account, String args) {
+ mContentsClient.getCallbackHelper().postOnReceivedLoginRequest(realm, account, args);
+ }
+ }
+
+ //--------------------------------------------------------------------------------------------
+ private class InterceptNavigationDelegateImpl implements InterceptNavigationDelegate {
+ private String mLastLoadUrlAddress;
+
+ public void onUrlLoadRequested(String url) {
+ mLastLoadUrlAddress = url;
+ }
+
+ @Override
+ public boolean shouldIgnoreNavigation(NavigationParams navigationParams) {
+ final String url = navigationParams.url;
+ boolean ignoreNavigation = false;
+ if (mLastLoadUrlAddress != null && mLastLoadUrlAddress.equals(url)) {
+ // Support the case where the user clicks on a link that takes them back to the
+ // same page.
+ mLastLoadUrlAddress = null;
+
+ // If the embedder requested the load of a certain URL via the loadUrl API, then we
+ // do not offer it to AwContentsClient.shouldOverrideUrlLoading.
+ // The embedder is also not allowed to intercept POST requests because of
+ // crbug.com/155250.
+ } else if (!navigationParams.isPost) {
+ ignoreNavigation = mContentsClient.shouldOverrideUrlLoading(url);
+ }
+
+ // The existing contract is that shouldOverrideUrlLoading callbacks are delivered before
+ // onPageStarted callbacks; third party apps depend on this behavior.
+ // Using a ResouceThrottle to implement the navigation interception feature results in
+ // the WebContentsObserver.didStartLoading callback happening before the
+ // ResourceThrottle has a chance to run.
+ // To preserve the ordering the onPageStarted callback is synthesized from the
+ // shouldOverrideUrlLoading, and only if the navigation was not ignored (this
+ // balances out with the onPageFinished callback, which is suppressed in the
+ // AwContentsClient if the navigation was ignored).
+ if (!ignoreNavigation) {
+ // The shouldOverrideUrlLoading call might have resulted in posting messages to the
+ // UI thread. Using sendMessage here (instead of calling onPageStarted directly)
+ // will allow those to run in order.
+ mContentsClient.getCallbackHelper().postOnPageStarted(url);
+ }
+
+ return ignoreNavigation;
+ }
+ }
+
+ //--------------------------------------------------------------------------------------------
+ private class AwLayoutSizerDelegate implements AwLayoutSizer.Delegate {
+ @Override
+ public void requestLayout() {
+ mContainerView.requestLayout();
+ }
+
+ @Override
+ public void setMeasuredDimension(int measuredWidth, int measuredHeight) {
+ mInternalAccessAdapter.setMeasuredDimension(measuredWidth, measuredHeight);
+ }
+ }
+
+ //--------------------------------------------------------------------------------------------
+ private class AwPinchGestureStateListener implements ContentViewCore.PinchGestureStateListener {
+ @Override
+ public void onPinchGestureStart() {
+ // While it's possible to re-layout the view during a pinch gesture, the effect is very
+ // janky (especially that the page scale update notification comes from the renderer
+ // main thread, not from the impl thread, so it's usually out of sync with what's on
+ // screen). It's also quite expensive to do a re-layout, so we simply postpone
+ // re-layout for the duration of the gesture. This is compatible with what
+ // WebViewClassic does.
+ mLayoutSizer.freezeLayoutRequests();
+ }
+
+ public void onPinchGestureEnd() {
+ mLayoutSizer.unfreezeLayoutRequests();
+ }
+ }
+
+ //--------------------------------------------------------------------------------------------
+ private class ScrollChangeListener implements ViewTreeObserver.OnScrollChangedListener {
+ @Override
+ public void onScrollChanged() {
+ // We do this to cover the case that when the view hierarchy is scrolled,
+ // more of the containing view becomes visible (i.e. a containing view
+ // with a width/height of "wrap_content" and dimensions greater than
+ // that of the screen).
+ AwContents.this.updatePhysicalBackingSizeIfNeeded();
+ }
+ };
+
+ private ScrollChangeListener mScrollChangeListener;
+
+ /**
+ * @param browserContext the browsing context to associate this view contents with.
+ * @param containerView the view-hierarchy item this object will be bound to.
+ * @param internalAccessAdapter to access private methods on containerView.
+ * @param contentsClient will receive API callbacks from this WebView Contents
+ * @param isAccessFromFileURLsGrantedByDefault passed to AwSettings.
+ *
+ * This constructor uses the default view sizing policy.
+ */
+ public AwContents(AwBrowserContext browserContext, ViewGroup containerView,
+ InternalAccessDelegate internalAccessAdapter, AwContentsClient contentsClient,
+ boolean isAccessFromFileURLsGrantedByDefault) {
+ this(browserContext, containerView, internalAccessAdapter, contentsClient,
+ isAccessFromFileURLsGrantedByDefault, new AwLayoutSizer());
+ }
+
+ private static ContentViewCore createAndInitializeContentViewCore(ViewGroup containerView,
+ InternalAccessDelegate internalDispatcher, int nativeWebContents,
+ ContentViewCore.PinchGestureStateListener pinchGestureStateListener,
+ ContentViewClient contentViewClient,
+ ContentViewCore.ZoomControlsDelegate zoomControlsDelegate) {
+ ContentViewCore contentViewCore = new ContentViewCore(containerView.getContext());
+ // Note INPUT_EVENTS_DELIVERED_IMMEDIATELY is passed to avoid triggering vsync in the
+ // compositor, not because input events are delivered immediately.
+ contentViewCore.initialize(containerView, internalDispatcher, nativeWebContents, null,
+ ContentViewCore.INPUT_EVENTS_DELIVERED_IMMEDIATELY);
+ contentViewCore.setPinchGestureStateListener(pinchGestureStateListener);
+ contentViewCore.setContentViewClient(contentViewClient);
+ contentViewCore.setZoomControlsDelegate(zoomControlsDelegate);
+ return contentViewCore;
+ }
+
+ /**
+ * @param layoutSizer the AwLayoutSizer instance implementing the sizing policy for the view.
+ *
+ * This version of the constructor is used in test code to inject test versions of the above
+ * documented classes
+ */
+ public AwContents(AwBrowserContext browserContext, ViewGroup containerView,
+ InternalAccessDelegate internalAccessAdapter, AwContentsClient contentsClient,
+ boolean isAccessFromFileURLsGrantedByDefault, AwLayoutSizer layoutSizer) {
+ mBrowserContext = browserContext;
+ mContainerView = containerView;
+ mInternalAccessAdapter = internalAccessAdapter;
+ mDIPScale = DeviceDisplayInfo.create(containerView.getContext()).getDIPScale();
+ // Note that ContentViewCore must be set up before AwContents, as ContentViewCore
+ // setup performs process initialisation work needed by AwContents.
+ mContentsClientBridge = new AwContentsClientBridge(contentsClient);
+ mLayoutSizer = layoutSizer;
+ mLayoutSizer.setDelegate(new AwLayoutSizerDelegate());
+ mLayoutSizer.setDIPScale(mDIPScale);
+ mWebContentsDelegate = new AwWebContentsDelegateAdapter(contentsClient,
+ mLayoutSizer.getPreferredSizeChangedListener());
+ mNativeAwContents = nativeInit(mWebContentsDelegate, mContentsClientBridge);
+ mContentsClient = contentsClient;
+ mCleanupReference = new CleanupReference(this, new DestroyRunnable(mNativeAwContents));
+
+ int nativeWebContents = nativeGetWebContents(mNativeAwContents);
+ mZoomControls = new AwZoomControls(this);
+ mContentViewCore = createAndInitializeContentViewCore(
+ containerView, internalAccessAdapter, nativeWebContents,
+ new AwPinchGestureStateListener(), mContentsClient.getContentViewClient(),
+ mZoomControls);
+ mContentsClient.installWebContentsObserver(mContentViewCore);
+
+ mSettings = new AwSettings(mContentViewCore.getContext(), nativeWebContents,
+ mContentViewCore, isAccessFromFileURLsGrantedByDefault);
+ setIoThreadClient(new IoThreadClientImpl());
+ setInterceptNavigationDelegate(new InterceptNavigationDelegateImpl());
+
+ mPossiblyStaleHitTestData = new HitTestData();
+ nativeDidInitializeContentViewCore(mNativeAwContents,
+ mContentViewCore.getNativeContentViewCore());
+
+ mContentsClient.setDIPScale(mDIPScale);
+ mSettings.setDIPScale(mDIPScale);
+ mDefaultVideoPosterRequestHandler = new DefaultVideoPosterRequestHandler(mContentsClient);
+ mSettings.setDefaultVideoPosterURL(
+ mDefaultVideoPosterRequestHandler.getDefaultVideoPosterURL());
+
+ ContentVideoView.registerContentVideoViewContextDelegate(
+ new AwContentVideoViewDelegate(contentsClient, containerView.getContext()));
+ mGlobalVisibleBounds = new Rect();
+ }
+
+ private void updatePhysicalBackingSizeIfNeeded() {
+ // We musn't let the physical backing size get too big, otherwise we
+ // will try to allocate a SurfaceTexture beyond what the GL driver can
+ // cope with. In most cases, limiting the SurfaceTexture size to that
+ // of the visible bounds of the WebView will be good enough i.e. the maximum
+ // SurfaceTexture dimensions will match the screen dimensions).
+ mContainerView.getGlobalVisibleRect(mGlobalVisibleBounds);
+ int width = mGlobalVisibleBounds.width();
+ int height = mGlobalVisibleBounds.height();
+ if (width != mLastGlobalVisibleWidth || height != mLastGlobalVisibleHeight) {
+ mLastGlobalVisibleWidth = width;
+ mLastGlobalVisibleHeight = height;
+ mContentViewCore.onPhysicalBackingSizeChanged(width, height);
+ }
+ }
+
+ @VisibleForTesting
+ public ContentViewCore getContentViewCore() {
+ return mContentViewCore;
+ }
+
+ // Can be called from any thread.
+ public AwSettings getSettings() {
+ return mSettings;
+ }
+
+ public void setIoThreadClient(AwContentsIoThreadClient ioThreadClient) {
+ mIoThreadClient = ioThreadClient;
+ nativeSetIoThreadClient(mNativeAwContents, mIoThreadClient);
+ }
+
+ private void setInterceptNavigationDelegate(InterceptNavigationDelegateImpl delegate) {
+ mInterceptNavigationDelegate = delegate;
+ nativeSetInterceptNavigationDelegate(mNativeAwContents, delegate);
+ }
+
+ public void destroy() {
+ mContentViewCore.destroy();
+ // The native part of AwSettings isn't needed for the IoThreadClient instance.
+ mSettings.destroy();
+ // We explicitly do not null out the mContentViewCore reference here
+ // because ContentViewCore already has code to deal with the case
+ // methods are called on it after it's been destroyed, and other
+ // code relies on AwContents.mContentViewCore to be non-null.
+ mCleanupReference.cleanupNow();
+ mNativeAwContents = 0;
+ }
+
+ public static void setAwDrawSWFunctionTable(int functionTablePointer) {
+ nativeSetAwDrawSWFunctionTable(functionTablePointer);
+ }
+
+ public static void setAwDrawGLFunctionTable(int functionTablePointer) {
+ nativeSetAwDrawGLFunctionTable(functionTablePointer);
+ }
+
+ public static int getAwDrawGLFunction() {
+ return nativeGetAwDrawGLFunction();
+ }
+
+ public int getAwDrawGLViewContext() {
+ // Using the native pointer as the returned viewContext. This is matched by the
+ // reinterpret_cast back to BrowserViewRenderer pointer in the native DrawGLFunction.
+ return nativeGetAwDrawGLViewContext(mNativeAwContents);
+ }
+
+ public void onDraw(Canvas canvas) {
+ if (mNativeAwContents == 0) return;
+ if (canvas.isHardwareAccelerated() &&
+ nativePrepareDrawGL(mNativeAwContents,
+ mContainerView.getScrollX(), mContainerView.getScrollY()) &&
+ mInternalAccessAdapter.requestDrawGL(canvas)) {
+ return;
+ }
+ Rect clip = canvas.getClipBounds();
+ if (!nativeDrawSW(mNativeAwContents, canvas, clip.left, clip.top,
+ clip.right - clip.left, clip.bottom - clip.top)) {
+ Log.w(TAG, "Native DrawSW failed; clearing to background color.");
+ int c = mContentViewCore.getBackgroundColor();
+ canvas.drawRGB(Color.red(c), Color.green(c), Color.blue(c));
+ }
+ }
+
+ @SuppressLint("WrongCall")
+ public void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
+ mLayoutSizer.onMeasure(widthMeasureSpec, heightMeasureSpec);
+ }
+
+ public int getContentHeightCss() {
+ return (int) Math.ceil(mContentViewCore.getContentHeightCss());
+ }
+
+ public int getContentWidthCss() {
+ return (int) Math.ceil(mContentViewCore.getContentWidthCss());
+ }
+
+ public Picture capturePicture() {
+ return nativeCapturePicture(mNativeAwContents);
+ }
+
+ /**
+ * Enable the OnNewPicture callback.
+ * @param enabled Flag to enable the callback.
+ * @param invalidationOnly Flag to call back only on invalidation without providing a picture.
+ */
+ public void enableOnNewPicture(boolean enabled, boolean invalidationOnly) {
+ mNewPictureInvalidationOnly = invalidationOnly;
+ nativeEnableOnNewPicture(mNativeAwContents, enabled);
+ }
+
+ // This is no longer synchronous and just calls the Async version and return 0.
+ // TODO(boliu): Remove this method.
+ @Deprecated
+ public int findAllSync(String searchString) {
+ findAllAsync(searchString);
+ return 0;
+ }
+
+ public void findAllAsync(String searchString) {
+ if (mNativeAwContents == 0) return;
+ nativeFindAllAsync(mNativeAwContents, searchString);
+ }
+
+ public void findNext(boolean forward) {
+ if (mNativeAwContents == 0) return;
+ nativeFindNext(mNativeAwContents, forward);
+ }
+
+ public void clearMatches() {
+ if (mNativeAwContents == 0) return;
+ nativeClearMatches(mNativeAwContents);
+ }
+
+ /**
+ * @return load progress of the WebContents.
+ */
+ public int getMostRecentProgress() {
+ // WebContentsDelegateAndroid conveniently caches the most recent notified value for us.
+ return mWebContentsDelegate.getMostRecentProgress();
+ }
+
+ public Bitmap getFavicon() {
+ return mFavicon;
+ }
+
+ private void requestVisitedHistoryFromClient() {
+ ValueCallback callback = new ValueCallback() {
+ @Override
+ public void onReceiveValue(final String[] value) {
+ ThreadUtils.runOnUiThread(new Runnable() {
+ @Override
+ public void run() {
+ if (mNativeAwContents == 0) return;
+ nativeAddVisitedLinks(mNativeAwContents, value);
+ }
+ });
+ }
+ };
+ mContentsClient.getVisitedHistory(callback);
+ }
+
+ /**
+ * Load url without fixing up the url string. Consumers of ContentView are responsible for
+ * ensuring the URL passed in is properly formatted (i.e. the scheme has been added if left
+ * off during user input).
+ *
+ * @param pararms Parameters for this load.
+ */
+ public void loadUrl(LoadUrlParams params) {
+ if (params.getLoadUrlType() == LoadUrlParams.LOAD_TYPE_DATA &&
+ !params.isBaseUrlDataScheme()) {
+ // This allows data URLs with a non-data base URL access to file:///android_asset/ and
+ // file:///android_res/ URLs. If AwSettings.getAllowFileAccess permits, it will also
+ // allow access to file:// URLs (subject to OS level permission checks).
+ params.setCanLoadLocalResources(true);
+ }
+
+ // If we are reloading the same url, then set transition type as reload.
+ if (params.getUrl() != null &&
+ params.getUrl().equals(mContentViewCore.getUrl()) &&
+ params.getTransitionType() == PageTransitionTypes.PAGE_TRANSITION_LINK) {
+ params.setTransitionType(PageTransitionTypes.PAGE_TRANSITION_RELOAD);
+ }
+
+ // For WebView, always use the user agent override, which is set
+ // every time the user agent in AwSettings is modified.
+ params.setOverrideUserAgent(LoadUrlParams.UA_OVERRIDE_TRUE);
+
+ mContentViewCore.loadUrl(params);
+
+ suppressInterceptionForThisNavigation();
+
+ // The behavior of WebViewClassic uses the populateVisitedLinks callback in WebKit.
+ // Chromium does not use this use code path and the best emulation of this behavior to call
+ // request visited links once on the first URL load of the WebView.
+ if (!mHasRequestedVisitedHistoryFromClient) {
+ mHasRequestedVisitedHistoryFromClient = true;
+ requestVisitedHistoryFromClient();
+ }
+ }
+
+ private void suppressInterceptionForThisNavigation() {
+ if (mInterceptNavigationDelegate != null) {
+ // getUrl returns a sanitized address in the same format that will be used for
+ // callbacks, so it's safe to use string comparison as an equality check later on.
+ mInterceptNavigationDelegate.onUrlLoadRequested(mContentViewCore.getUrl());
+ }
+ }
+
+ /**
+ * Get the URL of the current page.
+ *
+ * @return The URL of the current page or null if it's empty.
+ */
+ public String getUrl() {
+ String url = mContentViewCore.getUrl();
+ if (url == null || url.trim().isEmpty()) return null;
+ return url;
+ }
+ /**
+ * Called on the "source" AwContents that is opening the popup window to
+ * provide the AwContents to host the pop up content.
+ */
+ public void supplyContentsForPopup(AwContents newContents) {
+ int popupWebContents = nativeReleasePopupWebContents(mNativeAwContents);
+ assert popupWebContents != 0;
+ newContents.setNewWebContents(popupWebContents);
+ }
+
+ private void setNewWebContents(int newWebContentsPtr) {
+ // When setting a new WebContents, we new up a ContentViewCore that will
+ // wrap it and then swap it.
+ ContentViewCore newCore = createAndInitializeContentViewCore(
+ mContainerView, mInternalAccessAdapter, newWebContentsPtr,
+ new AwPinchGestureStateListener(), mContentsClient.getContentViewClient(),
+ mZoomControls);
+ mContentsClient.installWebContentsObserver(newCore);
+
+ // Now swap the Java side reference.
+ mContentViewCore.destroy();
+ mContentViewCore = newCore;
+
+ // Now rewire native side to use the new WebContents.
+ nativeSetWebContents(mNativeAwContents, newWebContentsPtr);
+ nativeSetIoThreadClient(mNativeAwContents, mIoThreadClient);
+ nativeSetInterceptNavigationDelegate(mNativeAwContents, mInterceptNavigationDelegate);
+
+ // This will also apply settings to the new WebContents.
+ mSettings.setWebContents(newWebContentsPtr);
+
+ // Finally poke the new ContentViewCore with the size of the container view and show it.
+ if (mContainerView.getWidth() != 0 || mContainerView.getHeight() != 0) {
+ mContentViewCore.onSizeChanged(
+ mContainerView.getWidth(), mContainerView.getHeight(), 0, 0);
+ }
+ nativeDidInitializeContentViewCore(mNativeAwContents,
+ mContentViewCore.getNativeContentViewCore());
+ if (mContainerView.getVisibility() == View.VISIBLE) {
+ // The popup window was hidden when we prompted the embedder to display
+ // it, so show it again now we have a container.
+ mContentViewCore.onShow();
+ }
+ }
+
+ public void requestFocus() {
+ if (!mContainerView.isInTouchMode() && mSettings.shouldFocusFirstNode()) {
+ nativeFocusFirstNode(mNativeAwContents);
+ }
+ }
+
+ public boolean isMultiTouchZoomSupported() {
+ return mSettings.supportsMultiTouchZoom();
+ }
+
+ public View getZoomControlsForTest() {
+ return mZoomControls.getZoomControlsViewForTest();
+ }
+
+ //--------------------------------------------------------------------------------------------
+ // WebView[Provider] method implementations (where not provided by ContentViewCore)
+ //--------------------------------------------------------------------------------------------
+
+ /**
+ * @see ContentViewCore#getContentSettings()
+ */
+ public ContentSettings getContentSettings() {
+ return mContentViewCore.getContentSettings();
+ }
+
+ /**
+ * @see ContentViewCore#computeHorizontalScrollRange()
+ */
+ public int computeHorizontalScrollRange() {
+ return mContentViewCore.computeHorizontalScrollRange();
+ }
+
+ /**
+ * @see ContentViewCore#computeHorizontalScrollOffset()
+ */
+ public int computeHorizontalScrollOffset() {
+ return mContentViewCore.computeHorizontalScrollOffset();
+ }
+
+ /**
+ * @see ContentViewCore#computeVerticalScrollRange()
+ */
+ public int computeVerticalScrollRange() {
+ return mContentViewCore.computeVerticalScrollRange();
+ }
+
+ /**
+ * @see ContentViewCore#computeVerticalScrollOffset()
+ */
+ public int computeVerticalScrollOffset() {
+ return mContentViewCore.computeVerticalScrollOffset();
+ }
+
+ /**
+ * @see ContentViewCore#computeVerticalScrollExtent()
+ */
+ public int computeVerticalScrollExtent() {
+ return mContentViewCore.computeVerticalScrollExtent();
+ }
+
+ /**
+ * @see android.webkit.WebView#stopLoading()
+ */
+ public void stopLoading() {
+ mContentViewCore.stopLoading();
+ }
+
+ /**
+ * @see android.webkit.WebView#reload()
+ */
+ public void reload() {
+ mContentViewCore.reload();
+ }
+
+ /**
+ * @see android.webkit.WebView#canGoBack()
+ */
+ public boolean canGoBack() {
+ return mContentViewCore.canGoBack();
+ }
+
+ /**
+ * @see android.webkit.WebView#goBack()
+ */
+ public void goBack() {
+ mContentViewCore.goBack();
+
+ suppressInterceptionForThisNavigation();
+ }
+
+ /**
+ * @see android.webkit.WebView#canGoForward()
+ */
+ public boolean canGoForward() {
+ return mContentViewCore.canGoForward();
+ }
+
+ /**
+ * @see android.webkit.WebView#goForward()
+ */
+ public void goForward() {
+ mContentViewCore.goForward();
+
+ suppressInterceptionForThisNavigation();
+ }
+
+ /**
+ * @see android.webkit.WebView#canGoBackOrForward(int)
+ */
+ public boolean canGoBackOrForward(int steps) {
+ return mContentViewCore.canGoToOffset(steps);
+ }
+
+ /**
+ * @see android.webkit.WebView#goBackOrForward(int)
+ */
+ public void goBackOrForward(int steps) {
+ mContentViewCore.goToOffset(steps);
+
+ suppressInterceptionForThisNavigation();
+ }
+
+ /**
+ * @see android.webkit.WebView#pauseTimers()
+ */
+ // TODO(kristianm): Remove
+ public void pauseTimers() {
+ ContentViewStatics.setWebKitSharedTimersSuspended(true);
+ }
+
+ /**
+ * @see android.webkit.WebView#resumeTimers()
+ */
+ // TODO(kristianm): Remove
+ public void resumeTimers() {
+ ContentViewStatics.setWebKitSharedTimersSuspended(false);
+ }
+
+ /**
+ * @see android.webkit.WebView#onPause()
+ */
+ public void onPause() {
+ mIsPaused = true;
+ mContentViewCore.onHide();
+ }
+
+ /**
+ * @see android.webkit.WebView#onResume()
+ */
+ public void onResume() {
+ mContentViewCore.onShow();
+ mIsPaused = false;
+ }
+
+ /**
+ * @see android.webkit.WebView#isPaused()
+ */
+ public boolean isPaused() {
+ return mIsPaused;
+ }
+
+ /**
+ * @see android.webkit.WebView#onCreateInputConnection(EditorInfo)
+ */
+ public InputConnection onCreateInputConnection(EditorInfo outAttrs) {
+ return mContentViewCore.onCreateInputConnection(outAttrs);
+ }
+
+ /**
+ * @see android.webkit.WebView#onKeyUp(int, KeyEvent)
+ */
+ public boolean onKeyUp(int keyCode, KeyEvent event) {
+ return mContentViewCore.onKeyUp(keyCode, event);
+ }
+
+ /**
+ * @see android.webkit.WebView#dispatchKeyEvent(KeyEvent)
+ */
+ public boolean dispatchKeyEvent(KeyEvent event) {
+ return mContentViewCore.dispatchKeyEvent(event);
+ }
+
+ /**
+ * Clears the resource cache. Note that the cache is per-application, so this will clear the
+ * cache for all WebViews used.
+ *
+ * @param includeDiskFiles if false, only the RAM cache is cleared
+ */
+ public void clearCache(boolean includeDiskFiles) {
+ if (mNativeAwContents == 0) return;
+ nativeClearCache(mNativeAwContents, includeDiskFiles);
+ }
+
+ public void documentHasImages(Message message) {
+ if (mNativeAwContents == 0) return;
+ nativeDocumentHasImages(mNativeAwContents, message);
+ }
+
+ public void saveWebArchive(
+ final String basename, boolean autoname, final ValueCallback callback) {
+ if (!autoname) {
+ saveWebArchiveInternal(basename, callback);
+ return;
+ }
+ // If auto-generating the file name, handle the name generation on a background thread
+ // as it will require I/O access for checking whether previous files existed.
+ new AsyncTask() {
+ @Override
+ protected String doInBackground(Void... params) {
+ return generateArchiveAutoNamePath(getOriginalUrl(), basename);
+ }
+
+ @Override
+ protected void onPostExecute(String result) {
+ saveWebArchiveInternal(result, callback);
+ }
+ }.execute();
+ }
+
+ public String getOriginalUrl() {
+ NavigationHistory history = mContentViewCore.getNavigationHistory();
+ int currentIndex = history.getCurrentEntryIndex();
+ if (currentIndex >= 0 && currentIndex < history.getEntryCount()) {
+ return history.getEntryAtIndex(currentIndex).getOriginalUrl();
+ }
+ return null;
+ }
+
+ /**
+ * @see ContentViewCore#getNavigationHistory()
+ */
+ public NavigationHistory getNavigationHistory() {
+ return mContentViewCore.getNavigationHistory();
+ }
+
+ /**
+ * @see android.webkit.WebView#getTitle()
+ */
+ public String getTitle() {
+ return mContentViewCore.getTitle();
+ }
+
+ /**
+ * @see android.webkit.WebView#clearHistory()
+ */
+ public void clearHistory() {
+ mContentViewCore.clearHistory();
+ }
+
+ public String[] getHttpAuthUsernamePassword(String host, String realm) {
+ return HttpAuthDatabase.getInstance(mContentViewCore.getContext())
+ .getHttpAuthUsernamePassword(host, realm);
+ }
+
+ public void setHttpAuthUsernamePassword(String host, String realm, String username,
+ String password) {
+ HttpAuthDatabase.getInstance(mContentViewCore.getContext())
+ .setHttpAuthUsernamePassword(host, realm, username, password);
+ }
+
+ /**
+ * @see android.webkit.WebView#getCertificate()
+ */
+ public SslCertificate getCertificate() {
+ if (mNativeAwContents == 0) return null;
+ return SslUtil.getCertificateFromDerBytes(nativeGetCertificate(mNativeAwContents));
+ }
+
+ /**
+ * @see android.webkit.WebView#clearSslPreferences()
+ */
+ public void clearSslPreferences() {
+ mContentViewCore.clearSslPreferences();
+ }
+
+ /**
+ * Method to return all hit test values relevant to public WebView API.
+ * Note that this expose more data than needed for WebView.getHitTestResult.
+ * Unsafely returning reference to mutable internal object to avoid excessive
+ * garbage allocation on repeated calls.
+ */
+ public HitTestData getLastHitTestResult() {
+ if (mNativeAwContents == 0) return null;
+ nativeUpdateLastHitTestData(mNativeAwContents);
+ return mPossiblyStaleHitTestData;
+ }
+
+ /**
+ * @see android.webkit.WebView#requestFocusNodeHref()
+ */
+ public void requestFocusNodeHref(Message msg) {
+ if (msg == null || mNativeAwContents == 0) return;
+
+ nativeUpdateLastHitTestData(mNativeAwContents);
+ Bundle data = msg.getData();
+ data.putString("url", mPossiblyStaleHitTestData.href);
+ data.putString("title", mPossiblyStaleHitTestData.anchorText);
+ data.putString("src", mPossiblyStaleHitTestData.imgSrc);
+ msg.setData(data);
+ msg.sendToTarget();
+ }
+
+ /**
+ * @see android.webkit.WebView#requestImageRef()
+ */
+ public void requestImageRef(Message msg) {
+ if (msg == null || mNativeAwContents == 0) return;
+
+ nativeUpdateLastHitTestData(mNativeAwContents);
+ Bundle data = msg.getData();
+ data.putString("url", mPossiblyStaleHitTestData.imgSrc);
+ msg.setData(data);
+ msg.sendToTarget();
+ }
+
+ /**
+ * @see android.webkit.WebView#getScale()
+ *
+ * Please note that the scale returned is the page scale multiplied by
+ * the screen density factor. See CTS WebViewTest.testSetInitialScale.
+ */
+ public float getScale() {
+ return (float)(mContentViewCore.getScale() * mDIPScale);
+ }
+
+ /**
+ * @see android.webkit.WebView#flingScroll(int, int)
+ */
+ public void flingScroll(int vx, int vy) {
+ mContentViewCore.flingScroll(vx, vy);
+ }
+
+ /**
+ * @see android.webkit.WebView#pageUp(boolean)
+ */
+ public boolean pageUp(boolean top) {
+ return mContentViewCore.pageUp(top);
+ }
+
+ /**
+ * @see android.webkit.WebView#pageDown(boolean)
+ */
+ public boolean pageDown(boolean bottom) {
+ return mContentViewCore.pageDown(bottom);
+ }
+
+ /**
+ * @see android.webkit.WebView#canZoomIn()
+ */
+ public boolean canZoomIn() {
+ return mContentViewCore.canZoomIn();
+ }
+
+ /**
+ * @see android.webkit.WebView#canZoomOut()
+ */
+ public boolean canZoomOut() {
+ return mContentViewCore.canZoomOut();
+ }
+
+ /**
+ * @see android.webkit.WebView#zoomIn()
+ */
+ public boolean zoomIn() {
+ return mContentViewCore.zoomIn();
+ }
+
+ /**
+ * @see android.webkit.WebView#zoomOut()
+ */
+ public boolean zoomOut() {
+ return mContentViewCore.zoomOut();
+ }
+
+ /**
+ * @see android.webkit.WebView#invokeZoomPicker()
+ */
+ public void invokeZoomPicker() {
+ mContentViewCore.invokeZoomPicker();
+ }
+
+ //--------------------------------------------------------------------------------------------
+ // View and ViewGroup method implementations
+ //--------------------------------------------------------------------------------------------
+
+ /**
+ * @see android.webkit.View#onTouchEvent()
+ */
+ public boolean onTouchEvent(MotionEvent event) {
+ if (mNativeAwContents == 0) return false;
+ boolean rv = mContentViewCore.onTouchEvent(event);
+
+ if (event.getActionMasked() == MotionEvent.ACTION_DOWN) {
+ int actionIndex = event.getActionIndex();
+
+ // Note this will trigger IPC back to browser even if nothing is hit.
+ nativeRequestNewHitTestDataAt(mNativeAwContents,
+ (int)Math.round(event.getX(actionIndex) / mDIPScale),
+ (int)Math.round(event.getY(actionIndex) / mDIPScale));
+ }
+
+ return rv;
+ }
+
+ /**
+ * @see android.view.View#onHoverEvent()
+ */
+ public boolean onHoverEvent(MotionEvent event) {
+ return mContentViewCore.onHoverEvent(event);
+ }
+
+ /**
+ * @see android.view.View#onGenericMotionEvent()
+ */
+ public boolean onGenericMotionEvent(MotionEvent event) {
+ return mContentViewCore.onGenericMotionEvent(event);
+ }
+
+ /**
+ * @see android.view.View#onConfigurationChanged()
+ */
+ public void onConfigurationChanged(Configuration newConfig) {
+ mContentViewCore.onConfigurationChanged(newConfig);
+ }
+
+ /**
+ * @see android.view.View#onAttachedToWindow()
+ */
+ public void onAttachedToWindow() {
+ if (mScrollChangeListener == null) {
+ mScrollChangeListener = new ScrollChangeListener();
+ }
+ mContainerView.getViewTreeObserver().addOnScrollChangedListener(mScrollChangeListener);
+
+ mContentViewCore.onAttachedToWindow();
+ nativeOnAttachedToWindow(mNativeAwContents, mContainerView.getWidth(),
+ mContainerView.getHeight());
+
+ // This is for the case where this is created by restoreState, which
+ // needs to call to NavigationController::LoadIfNecessary to actually
+ // load the restored page.
+ if (!mIsPaused) onResume();
+ }
+
+ /**
+ * @see android.view.View#onDetachedFromWindow()
+ */
+ public void onDetachedFromWindow() {
+ if (mNativeAwContents != 0) {
+ nativeOnDetachedFromWindow(mNativeAwContents);
+ }
+
+ if (mScrollChangeListener != null) {
+ mContainerView.getViewTreeObserver().removeOnScrollChangedListener(
+ mScrollChangeListener);
+ mScrollChangeListener = null;
+ }
+
+ mContentViewCore.onDetachedFromWindow();
+ }
+
+ /**
+ * @see android.view.View#onWindowFocusChanged()
+ */
+ public void onWindowFocusChanged(boolean hasWindowFocus) {
+ mWindowFocused = hasWindowFocus;
+ mContentViewCore.onFocusChanged(mContainerViewFocused && mWindowFocused);
+ }
+
+ /**
+ * @see android.view.View#onFocusChanged()
+ */
+ public void onFocusChanged(boolean focused, int direction, Rect previouslyFocusedRect) {
+ mContainerViewFocused = focused;
+ mContentViewCore.onFocusChanged(mContainerViewFocused && mWindowFocused);
+ }
+
+ /**
+ * @see android.view.View#onSizeChanged()
+ */
+ public void onSizeChanged(int w, int h, int ow, int oh) {
+ if (mNativeAwContents == 0) return;
+ updatePhysicalBackingSizeIfNeeded();
+ mContentViewCore.onSizeChanged(w, h, ow, oh);
+ nativeOnSizeChanged(mNativeAwContents, w, h, ow, oh);
+ }
+
+ /**
+ * @see android.view.View#onVisibilityChanged()
+ */
+ public void onVisibilityChanged(View changedView, int visibility) {
+ updateVisiblityState();
+ }
+
+ /**
+ * @see android.view.View#onWindowVisibilityChanged()
+ */
+ public void onWindowVisibilityChanged(int visibility) {
+ updateVisiblityState();
+ }
+
+ private void updateVisiblityState() {
+ if (mNativeAwContents == 0 || mIsPaused) return;
+ boolean windowVisible = mContainerView.getWindowVisibility() == View.VISIBLE;
+ boolean viewVisible = mContainerView.getVisibility() == View.VISIBLE;
+ nativeSetWindowViewVisibility(mNativeAwContents, windowVisible, viewVisible);
+
+ if (viewVisible) {
+ mContentViewCore.onShow();
+ } else {
+ mContentViewCore.onHide();
+ }
+ }
+
+
+ /**
+ * Key for opaque state in bundle. Note this is only public for tests.
+ */
+ public static final String SAVE_RESTORE_STATE_KEY = "WEBVIEW_CHROMIUM_STATE";
+
+ /**
+ * Save the state of this AwContents into provided Bundle.
+ * @return False if saving state failed.
+ */
+ public boolean saveState(Bundle outState) {
+ if (outState == null) return false;
+
+ byte[] state = nativeGetOpaqueState(mNativeAwContents);
+ if (state == null) return false;
+
+ outState.putByteArray(SAVE_RESTORE_STATE_KEY, state);
+ return true;
+ }
+
+ /**
+ * Restore the state of this AwContents into provided Bundle.
+ * @param inState Must be a bundle returned by saveState.
+ * @return False if restoring state failed.
+ */
+ public boolean restoreState(Bundle inState) {
+ if (inState == null) return false;
+
+ byte[] state = inState.getByteArray(SAVE_RESTORE_STATE_KEY);
+ if (state == null) return false;
+
+ boolean result = nativeRestoreFromOpaqueState(mNativeAwContents, state);
+
+ // The onUpdateTitle callback normally happens when a page is loaded,
+ // but is optimized out in the restoreState case because the title is
+ // already restored. See WebContentsImpl::UpdateTitleForEntry. So we
+ // call the callback explicitly here.
+ if (result) mContentsClient.onReceivedTitle(mContentViewCore.getTitle());
+
+ return result;
+ }
+
+ /**
+ * @see ContentViewCore#addPossiblyUnsafeJavascriptInterface(Object, String, Class)
+ */
+ public void addPossiblyUnsafeJavascriptInterface(Object object, String name,
+ Class extends Annotation> requiredAnnotation) {
+ mContentViewCore.addPossiblyUnsafeJavascriptInterface(object, name, requiredAnnotation);
+ }
+
+ /**
+ * @see android.webkit.WebView#removeJavascriptInterface(String)
+ */
+ public void removeJavascriptInterface(String interfaceName) {
+ mContentViewCore.removeJavascriptInterface(interfaceName);
+ }
+
+ /**
+ * @see android.webkit.WebView#onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo)
+ */
+ public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) {
+ mContentViewCore.onInitializeAccessibilityNodeInfo(info);
+ }
+
+ /**
+ * @see android.webkit.WebView#onInitializeAccessibilityEvent(AccessibilityEvent)
+ */
+ public void onInitializeAccessibilityEvent(AccessibilityEvent event) {
+ mContentViewCore.onInitializeAccessibilityEvent(event);
+ }
+
+ public boolean supportsAccessibilityAction(int action) {
+ return mContentViewCore.supportsAccessibilityAction(action);
+ }
+
+ /**
+ * @see android.webkit.WebView#performAccessibilityAction(int, Bundle)
+ */
+ public boolean performAccessibilityAction(int action, Bundle arguments) {
+ return mContentViewCore.performAccessibilityAction(action, arguments);
+ }
+
+ //--------------------------------------------------------------------------------------------
+ // Methods called from native via JNI
+ //--------------------------------------------------------------------------------------------
+
+ @CalledByNative
+ private static void onDocumentHasImagesResponse(boolean result, Message message) {
+ message.arg1 = result ? 1 : 0;
+ message.sendToTarget();
+ }
+
+ @CalledByNative
+ private void onReceivedTouchIconUrl(String url, boolean precomposed) {
+ mContentsClient.onReceivedTouchIconUrl(url, precomposed);
+ }
+
+ @CalledByNative
+ private void onReceivedIcon(Bitmap bitmap) {
+ mContentsClient.onReceivedIcon(bitmap);
+ mFavicon = bitmap;
+ }
+
+ /** Callback for generateMHTML. */
+ @CalledByNative
+ private static void generateMHTMLCallback(
+ String path, long size, ValueCallback callback) {
+ if (callback == null) return;
+ callback.onReceiveValue(size < 0 ? null : path);
+ }
+
+ @CalledByNative
+ private void onReceivedHttpAuthRequest(AwHttpAuthHandler handler, String host, String realm) {
+ mContentsClient.onReceivedHttpAuthRequest(handler, host, realm);
+ }
+
+ private class AwGeolocationCallback implements GeolocationPermissions.Callback {
+
+ @Override
+ public void invoke(final String origin, final boolean allow, final boolean retain) {
+ ThreadUtils.runOnUiThread(new Runnable() {
+ @Override
+ public void run() {
+ if (retain) {
+ if (allow) {
+ mBrowserContext.getGeolocationPermissions().allow(origin);
+ } else {
+ mBrowserContext.getGeolocationPermissions().deny(origin);
+ }
+ }
+ nativeInvokeGeolocationCallback(mNativeAwContents, allow, origin);
+ }
+ });
+ }
+ }
+
+ @CalledByNative
+ private void onGeolocationPermissionsShowPrompt(String origin) {
+ AwGeolocationPermissions permissions = mBrowserContext.getGeolocationPermissions();
+ // Reject if geoloaction is disabled, or the origin has a retained deny
+ if (!mSettings.getGeolocationEnabled()) {
+ nativeInvokeGeolocationCallback(mNativeAwContents, false, origin);
+ return;
+ }
+ // Allow if the origin has a retained allow
+ if (permissions.hasOrigin(origin)) {
+ nativeInvokeGeolocationCallback(mNativeAwContents, permissions.isOriginAllowed(origin),
+ origin);
+ return;
+ }
+ mContentsClient.onGeolocationPermissionsShowPrompt(
+ origin, new AwGeolocationCallback());
+ }
+
+ @CalledByNative
+ private void onGeolocationPermissionsHidePrompt() {
+ mContentsClient.onGeolocationPermissionsHidePrompt();
+ }
+
+ @CalledByNative
+ public void onFindResultReceived(int activeMatchOrdinal, int numberOfMatches,
+ boolean isDoneCounting) {
+ mContentsClient.onFindResultReceived(activeMatchOrdinal, numberOfMatches, isDoneCounting);
+ }
+
+ @CalledByNative
+ public void onNewPicture() {
+ mContentsClient.onNewPicture(mNewPictureInvalidationOnly ? null : capturePicture());
+ }
+
+ // Called as a result of nativeUpdateLastHitTestData.
+ @CalledByNative
+ private void updateHitTestData(
+ int type, String extra, String href, String anchorText, String imgSrc) {
+ mPossiblyStaleHitTestData.hitTestResultType = type;
+ mPossiblyStaleHitTestData.hitTestResultExtraData = extra;
+ mPossiblyStaleHitTestData.href = href;
+ mPossiblyStaleHitTestData.anchorText = anchorText;
+ mPossiblyStaleHitTestData.imgSrc = imgSrc;
+ }
+
+ @CalledByNative
+ private void requestProcessMode() {
+ mInternalAccessAdapter.requestDrawGL(null);
+ }
+
+ @CalledByNative
+ private void invalidate() {
+ mContainerView.invalidate();
+ }
+
+ @CalledByNative
+ private boolean performLongClick() {
+ return mContainerView.performLongClick();
+ }
+
+ @CalledByNative
+ private int[] getLocationOnScreen() {
+ int[] result = new int[2];
+ mContainerView.getLocationOnScreen(result);
+ return result;
+ }
+
+ @CalledByNative
+ private void onPageScaleFactorChanged(float pageScaleFactor) {
+ // This change notification comes from the renderer thread, not from the cc/ impl thread.
+ mLayoutSizer.onPageScaleChanged(pageScaleFactor);
+ }
+
+ // -------------------------------------------------------------------------------------------
+ // Helper methods
+ // -------------------------------------------------------------------------------------------
+
+ private void saveWebArchiveInternal(String path, final ValueCallback callback) {
+ if (path == null || mNativeAwContents == 0) {
+ ThreadUtils.runOnUiThread(new Runnable() {
+ @Override
+ public void run() {
+ callback.onReceiveValue(null);
+ }
+ });
+ } else {
+ nativeGenerateMHTML(mNativeAwContents, path, callback);
+ }
+ }
+
+ /**
+ * Try to generate a pathname for saving an MHTML archive. This roughly follows WebView's
+ * autoname logic.
+ */
+ private static String generateArchiveAutoNamePath(String originalUrl, String baseName) {
+ String name = null;
+ if (originalUrl != null && !originalUrl.isEmpty()) {
+ try {
+ String path = new URL(originalUrl).getPath();
+ int lastSlash = path.lastIndexOf('/');
+ if (lastSlash > 0) {
+ name = path.substring(lastSlash + 1);
+ } else {
+ name = path;
+ }
+ } catch (MalformedURLException e) {
+ // If it fails parsing the URL, we'll just rely on the default name below.
+ }
+ }
+
+ if (TextUtils.isEmpty(name)) name = "index";
+
+ String testName = baseName + name + WEB_ARCHIVE_EXTENSION;
+ if (!new File(testName).exists()) return testName;
+
+ for (int i = 1; i < 100; i++) {
+ testName = baseName + name + "-" + i + WEB_ARCHIVE_EXTENSION;
+ if (!new File(testName).exists()) return testName;
+ }
+
+ Log.e(TAG, "Unable to auto generate archive name for path: " + baseName);
+ return null;
+ }
+
+ //--------------------------------------------------------------------------------------------
+ // Native methods
+ //--------------------------------------------------------------------------------------------
+
+ private native int nativeInit(AwWebContentsDelegate webViewWebContentsDelegate,
+ AwContentsClientBridge contentsClientBridge);
+ private static native void nativeDestroy(int nativeAwContents);
+ private static native void nativeSetAwDrawSWFunctionTable(int functionTablePointer);
+ private static native void nativeSetAwDrawGLFunctionTable(int functionTablePointer);
+ private static native int nativeGetAwDrawGLFunction();
+
+ private native int nativeGetWebContents(int nativeAwContents);
+ private native void nativeDidInitializeContentViewCore(int nativeAwContents,
+ int nativeContentViewCore);
+
+ private native void nativeDocumentHasImages(int nativeAwContents, Message message);
+ private native void nativeGenerateMHTML(
+ int nativeAwContents, String path, ValueCallback callback);
+
+ private native void nativeSetIoThreadClient(int nativeAwContents,
+ AwContentsIoThreadClient ioThreadClient);
+ private native void nativeSetInterceptNavigationDelegate(int nativeAwContents,
+ InterceptNavigationDelegate navigationInterceptionDelegate);
+
+ private native void nativeAddVisitedLinks(int nativeAwContents, String[] visitedLinks);
+
+ private native boolean nativePrepareDrawGL(int nativeAwContents, int scrollX, int scrollY);
+ private native void nativeFindAllAsync(int nativeAwContents, String searchString);
+ private native void nativeFindNext(int nativeAwContents, boolean forward);
+ private native void nativeClearMatches(int nativeAwContents);
+ private native void nativeClearCache(int nativeAwContents, boolean includeDiskFiles);
+ private native byte[] nativeGetCertificate(int nativeAwContents);
+
+ // Coordinates in desity independent pixels.
+ private native void nativeRequestNewHitTestDataAt(int nativeAwContents, int x, int y);
+ private native void nativeUpdateLastHitTestData(int nativeAwContents);
+
+ private native void nativeOnSizeChanged(int nativeAwContents, int w, int h, int ow, int oh);
+ private native void nativeSetWindowViewVisibility(int nativeAwContents, boolean windowVisible,
+ boolean viewVisible);
+ private native void nativeOnAttachedToWindow(int nativeAwContents, int w, int h);
+ private native void nativeOnDetachedFromWindow(int nativeAwContents);
+
+ // Returns null if save state fails.
+ private native byte[] nativeGetOpaqueState(int nativeAwContents);
+
+ // Returns false if restore state fails.
+ private native boolean nativeRestoreFromOpaqueState(int nativeAwContents, byte[] state);
+
+ private native int nativeReleasePopupWebContents(int nativeAwContents);
+ private native void nativeSetWebContents(int nativeAwContents, int nativeNewWebContents);
+ private native void nativeFocusFirstNode(int nativeAwContents);
+
+ private native boolean nativeDrawSW(int nativeAwContents, Canvas canvas, int clipX, int clipY,
+ int clipW, int clipH);
+ private native int nativeGetAwDrawGLViewContext(int nativeAwContents);
+ private native Picture nativeCapturePicture(int nativeAwContents);
+ private native void nativeEnableOnNewPicture(int nativeAwContents, boolean enabled);
+
+ private native void nativeInvokeGeolocationCallback(
+ int nativeAwContents, boolean value, String requestingFrame);
+}
diff --git a/src/org/chromium/android_webview/AwContentsClient.java b/src/org/chromium/android_webview/AwContentsClient.java
new file mode 100644
index 0000000..70cd68c
--- /dev/null
+++ b/src/org/chromium/android_webview/AwContentsClient.java
@@ -0,0 +1,240 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+package org.chromium.android_webview;
+
+import android.content.pm.ActivityInfo;
+import android.content.Context;
+import android.graphics.Bitmap;
+import android.graphics.Picture;
+import android.graphics.Rect;
+import android.graphics.RectF;
+import android.net.http.SslError;
+import android.os.Handler;
+import android.os.Looper;
+import android.os.Message;
+import android.util.Log;
+import android.view.KeyEvent;
+import android.view.View;
+import android.webkit.ConsoleMessage;
+import android.webkit.GeolocationPermissions;
+import android.webkit.SslErrorHandler;
+import android.webkit.ValueCallback;
+import android.webkit.WebChromeClient;
+
+import org.chromium.content.browser.ContentViewClient;
+import org.chromium.content.browser.ContentViewCore;
+import org.chromium.content.browser.WebContentsObserverAndroid;
+import org.chromium.net.NetError;
+
+/**
+ * Base-class that an AwContents embedder derives from to receive callbacks.
+ * This extends ContentViewClient, as in many cases we want to pass-thru ContentViewCore
+ * callbacks right to our embedder, and this setup facilities that.
+ * For any other callbacks we need to make transformations of (e.g. adapt parameters
+ * or perform filtering) we can provide final overrides for methods here, and then introduce
+ * new abstract methods that the our own client must implement.
+ * i.e.: all methods in this class should either be final, or abstract.
+ */
+public abstract class AwContentsClient {
+
+ private static final String TAG = "AwContentsClient";
+ private final AwContentsClientCallbackHelper mCallbackHelper =
+ new AwContentsClientCallbackHelper(this);
+
+ private AwWebContentsObserver mWebContentsObserver;
+
+ private AwContentViewClient mContentViewClient = new AwContentViewClient();
+
+ private double mDIPScale;
+
+ class AwWebContentsObserver extends WebContentsObserverAndroid {
+ public AwWebContentsObserver(ContentViewCore contentViewCore) {
+ super(contentViewCore);
+ }
+
+ @Override
+ public void didStopLoading(String url) {
+ AwContentsClient.this.onPageFinished(url);
+ }
+
+ @Override
+ public void didFailLoad(boolean isProvisionalLoad,
+ boolean isMainFrame, int errorCode, String description, String failingUrl) {
+ if (errorCode == NetError.ERR_ABORTED) {
+ // This error code is generated for the following reasons:
+ // - WebView.stopLoading is called,
+ // - the navigation is intercepted by the embedder via shouldOverrideNavigation.
+ //
+ // The Android WebView does not notify the embedder of these situations using this
+ // error code with the WebViewClient.onReceivedError callback.
+ return;
+ }
+ if (!isMainFrame) {
+ // The Android WebView does not notify the embedder of sub-frame failures.
+ return;
+ }
+ AwContentsClient.this.onReceivedError(
+ ErrorCodeConversionHelper.convertErrorCode(errorCode), description, failingUrl);
+ }
+
+ @Override
+ public void didNavigateAnyFrame(String url, String baseUrl, boolean isReload) {
+ AwContentsClient.this.doUpdateVisitedHistory(url, isReload);
+ }
+
+ }
+
+ private class AwContentViewClient extends ContentViewClient {
+
+ @Override
+ public void onScaleChanged(float oldScale, float newScale) {
+ AwContentsClient.this.onScaleChangedScaled((float)(oldScale * mDIPScale),
+ (float)(newScale * mDIPScale));
+ }
+
+ @Override
+ public void onStartContentIntent(Context context, String contentUrl) {
+ // Callback when detecting a click on a content link.
+ AwContentsClient.this.shouldOverrideUrlLoading(contentUrl);
+ }
+
+ @Override
+ public void onTabCrash() {
+ // This is not possible so long as the webview is run single process!
+ throw new RuntimeException("Renderer crash reported.");
+ }
+
+ @Override
+ public void onUpdateTitle(String title) {
+ AwContentsClient.this.onReceivedTitle(title);
+ }
+
+ @Override
+ public boolean shouldOverrideKeyEvent(KeyEvent event) {
+ return AwContentsClient.this.shouldOverrideKeyEvent(event);
+ }
+
+ }
+
+ final void installWebContentsObserver(ContentViewCore contentViewCore) {
+ if (mWebContentsObserver != null) {
+ mWebContentsObserver.detachFromWebContents();
+ }
+ mWebContentsObserver = new AwWebContentsObserver(contentViewCore);
+ }
+
+ final void setDIPScale(double dipScale) {
+ mDIPScale = dipScale;
+ }
+
+ final AwContentsClientCallbackHelper getCallbackHelper() {
+ return mCallbackHelper;
+ }
+
+ final ContentViewClient getContentViewClient() {
+ return mContentViewClient;
+ }
+
+ //--------------------------------------------------------------------------------------------
+ // WebView specific methods that map directly to WebViewClient / WebChromeClient
+ //--------------------------------------------------------------------------------------------
+
+ public abstract void getVisitedHistory(ValueCallback callback);
+
+ public abstract void doUpdateVisitedHistory(String url, boolean isReload);
+
+ public abstract void onProgressChanged(int progress);
+
+ public abstract InterceptedRequestData shouldInterceptRequest(String url);
+
+ public abstract boolean shouldOverrideKeyEvent(KeyEvent event);
+
+ public abstract boolean shouldOverrideUrlLoading(String url);
+
+ public abstract void onLoadResource(String url);
+
+ public abstract void onUnhandledKeyEvent(KeyEvent event);
+
+ public abstract boolean onConsoleMessage(ConsoleMessage consoleMessage);
+
+ public abstract void onReceivedHttpAuthRequest(AwHttpAuthHandler handler,
+ String host, String realm);
+
+ public abstract void onReceivedSslError(ValueCallback callback, SslError error);
+
+ public abstract void onReceivedLoginRequest(String realm, String account, String args);
+
+ public abstract void onFormResubmission(Message dontResend, Message resend);
+
+ public abstract void onDownloadStart(String url, String userAgent, String contentDisposition,
+ String mimeType, long contentLength);
+
+ public abstract void onGeolocationPermissionsShowPrompt(String origin,
+ GeolocationPermissions.Callback callback);
+
+ public abstract void onGeolocationPermissionsHidePrompt();
+
+ public abstract void onScaleChangedScaled(float oldScale, float newScale);
+
+ protected abstract void handleJsAlert(String url, String message, JsResultReceiver receiver);
+
+ protected abstract void handleJsBeforeUnload(String url, String message,
+ JsResultReceiver receiver);
+
+ protected abstract void handleJsConfirm(String url, String message, JsResultReceiver receiver);
+
+ protected abstract void handleJsPrompt(String url, String message, String defaultValue,
+ JsPromptResultReceiver receiver);
+
+ protected abstract boolean onCreateWindow(boolean isDialog, boolean isUserGesture);
+
+ protected abstract void onCloseWindow();
+
+ public abstract void onReceivedTouchIconUrl(String url, boolean precomposed);
+
+ public abstract void onReceivedIcon(Bitmap bitmap);
+
+ public abstract void onReceivedTitle(String title);
+
+ protected abstract void onRequestFocus();
+
+ protected abstract View getVideoLoadingProgressView();
+
+ public abstract void onPageStarted(String url);
+
+ public abstract void onPageFinished(String url);
+
+ public abstract void onReceivedError(int errorCode, String description, String failingUrl);
+
+ // TODO (michaelbai): Remove this method once the same method remove from
+ // WebViewContentsClientAdapter.
+ public void onShowCustomView(View view,
+ int requestedOrientation, WebChromeClient.CustomViewCallback callback) {
+ }
+
+ // TODO (michaelbai): This method should be abstract, having empty body here
+ // makes the merge to the Android easy.
+ public void onShowCustomView(View view, WebChromeClient.CustomViewCallback callback) {
+ onShowCustomView(view, ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED, callback);
+ }
+
+ public abstract void onHideCustomView();
+
+ public abstract Bitmap getDefaultVideoPoster();
+
+ //--------------------------------------------------------------------------------------------
+ // Other WebView-specific methods
+ //--------------------------------------------------------------------------------------------
+ //
+ public abstract void onFindResultReceived(int activeMatchOrdinal, int numberOfMatches,
+ boolean isDoneCounting);
+
+ /**
+ * Called whenever there is a new content picture available.
+ * @param picture New picture.
+ */
+ public abstract void onNewPicture(Picture picture);
+
+}
diff --git a/src/org/chromium/android_webview/AwContentsClientBridge.java b/src/org/chromium/android_webview/AwContentsClientBridge.java
new file mode 100644
index 0000000..7172cfb
--- /dev/null
+++ b/src/org/chromium/android_webview/AwContentsClientBridge.java
@@ -0,0 +1,113 @@
+// Copyright (c) 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+package org.chromium.android_webview;
+
+import android.net.http.SslCertificate;
+import android.net.http.SslError;
+import android.webkit.ValueCallback;
+
+import org.chromium.base.CalledByNative;
+import org.chromium.base.JNINamespace;
+import org.chromium.base.ThreadUtils;
+
+/**
+ * This class handles the JNI communication logic for the the AwContentsClient class.
+ * Both the Java and the native peers of AwContentsClientBridge are owned by the
+ * corresponding AwContents instances. This class and its native peer are connected
+ * via weak references. The native AwContentsClientBridge sets up and clear these weak
+ * references.
+ */
+@JNINamespace("android_webview")
+public class AwContentsClientBridge {
+
+ private AwContentsClient mClient;
+ // The native peer of this object.
+ private int mNativeContentsClientBridge;
+
+ public AwContentsClientBridge(AwContentsClient client) {
+ assert client != null;
+ mClient = client;
+ }
+
+ // Used by the native peer to set/reset a weak ref to the native peer.
+ @CalledByNative
+ private void setNativeContentsClientBridge(int nativeContentsClientBridge) {
+ mNativeContentsClientBridge = nativeContentsClientBridge;
+ }
+
+ // If returns false, the request is immediately canceled, and any call to proceedSslError
+ // has no effect. If returns true, the request should be canceled or proceeded using
+ // proceedSslError().
+ // Unlike the webview classic, we do not keep keep a database of certificates that
+ // are allowed by the user, because this functionality is already handled via
+ // ssl_policy in native layers.
+ @CalledByNative
+ private boolean allowCertificateError(int certError, byte[] derBytes, final String url,
+ final int id) {
+ final SslCertificate cert = SslUtil.getCertificateFromDerBytes(derBytes);
+ if (cert == null) {
+ // if the certificate or the client is null, cancel the request
+ return false;
+ }
+ final SslError sslError = SslUtil.sslErrorFromNetErrorCode(certError, cert, url);
+ ValueCallback callback = new ValueCallback() {
+ @Override
+ public void onReceiveValue(Boolean value) {
+ proceedSslError(value.booleanValue(), id);
+ }
+ };
+ mClient.onReceivedSslError(callback, sslError);
+ return true;
+ }
+
+ private void proceedSslError(boolean proceed, int id) {
+ if (mNativeContentsClientBridge == 0) return;
+ nativeProceedSslError(mNativeContentsClientBridge, proceed, id);
+ }
+
+ @CalledByNative
+ private void handleJsAlert(String url, String message, int id) {
+ JsResultHandler handler = new JsResultHandler(this, id);
+ mClient.handleJsAlert(url, message, handler);
+ }
+
+ @CalledByNative
+ private void handleJsConfirm(String url, String message, int id) {
+ JsResultHandler handler = new JsResultHandler(this, id);
+ mClient.handleJsConfirm(url, message, handler);
+ }
+
+ @CalledByNative
+ private void handleJsPrompt(String url, String message, String defaultValue, int id) {
+ JsResultHandler handler = new JsResultHandler(this, id);
+ mClient.handleJsPrompt(url, message, defaultValue, handler);
+ }
+
+ @CalledByNative
+ private void handleJsBeforeUnload(String url, String message, int id) {
+ JsResultHandler handler = new JsResultHandler(this, id);
+ mClient.handleJsBeforeUnload(url, message, handler);
+ }
+
+ void confirmJsResult(int id, String prompt) {
+ if (mNativeContentsClientBridge == 0) return;
+ nativeConfirmJsResult(mNativeContentsClientBridge, id, prompt);
+ }
+
+ void cancelJsResult(int id) {
+ if (mNativeContentsClientBridge == 0) return;
+ nativeCancelJsResult(mNativeContentsClientBridge, id);
+ }
+
+ //--------------------------------------------------------------------------------------------
+ // Native methods
+ //--------------------------------------------------------------------------------------------
+ private native void nativeProceedSslError(int nativeAwContentsClientBridge, boolean proceed,
+ int id);
+
+ private native void nativeConfirmJsResult(int nativeAwContentsClientBridge, int id,
+ String prompt);
+ private native void nativeCancelJsResult(int nativeAwContentsClientBridge, int id);
+}
\ No newline at end of file
diff --git a/src/org/chromium/android_webview/AwContentsClientCallbackHelper.java b/src/org/chromium/android_webview/AwContentsClientCallbackHelper.java
new file mode 100644
index 0000000..8dbd571
--- /dev/null
+++ b/src/org/chromium/android_webview/AwContentsClientCallbackHelper.java
@@ -0,0 +1,142 @@
+// Copyright (c) 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+package org.chromium.android_webview;
+
+import android.os.Handler;
+import android.os.Looper;
+import android.os.Message;
+
+import org.chromium.content.browser.ContentViewCore;
+
+/**
+ * This class is responsible for calling certain client callbacks on the UI thread.
+ *
+ * Most callbacks do no go through here, but get forwarded to AwContentsClient directly. The
+ * messages processed here may originate from the IO or UI thread.
+ */
+class AwContentsClientCallbackHelper {
+
+ // TODO(boliu): Consider removing DownloadInfo and LoginRequestInfo by using native
+ // MessageLoop to post directly to AwContents.
+
+ private static class DownloadInfo {
+ final String mUrl;
+ final String mUserAgent;
+ final String mContentDisposition;
+ final String mMimeType;
+ final long mContentLength;
+
+ DownloadInfo(String url,
+ String userAgent,
+ String contentDisposition,
+ String mimeType,
+ long contentLength) {
+ mUrl = url;
+ mUserAgent = userAgent;
+ mContentDisposition = contentDisposition;
+ mMimeType = mimeType;
+ mContentLength = contentLength;
+ }
+ }
+
+ private static class LoginRequestInfo {
+ final String mRealm;
+ final String mAccount;
+ final String mArgs;
+
+ LoginRequestInfo(String realm, String account, String args) {
+ mRealm = realm;
+ mAccount = account;
+ mArgs = args;
+ }
+ }
+
+ private static class OnReceivedErrorInfo {
+ final int mErrorCode;
+ final String mDescription;
+ final String mFailingUrl;
+
+ OnReceivedErrorInfo(int errorCode, String description, String failingUrl) {
+ mErrorCode = errorCode;
+ mDescription = description;
+ mFailingUrl = failingUrl;
+ }
+ }
+
+ private final static int MSG_ON_LOAD_RESOURCE = 1;
+ private final static int MSG_ON_PAGE_STARTED = 2;
+ private final static int MSG_ON_DOWNLOAD_START = 3;
+ private final static int MSG_ON_RECEIVED_LOGIN_REQUEST = 4;
+ private final static int MSG_ON_RECEIVED_ERROR = 5;
+
+ private final AwContentsClient mContentsClient;
+
+ private final Handler mHandler = new Handler(Looper.getMainLooper()) {
+ @Override
+ public void handleMessage(Message msg) {
+ switch(msg.what) {
+ case MSG_ON_LOAD_RESOURCE: {
+ final String url = (String) msg.obj;
+ mContentsClient.onLoadResource(url);
+ break;
+ }
+ case MSG_ON_PAGE_STARTED: {
+ final String url = (String) msg.obj;
+ mContentsClient.onPageStarted(url);
+ break;
+ }
+ case MSG_ON_DOWNLOAD_START: {
+ DownloadInfo info = (DownloadInfo) msg.obj;
+ mContentsClient.onDownloadStart(info.mUrl, info.mUserAgent,
+ info.mContentDisposition, info.mMimeType, info.mContentLength);
+ break;
+ }
+ case MSG_ON_RECEIVED_LOGIN_REQUEST: {
+ LoginRequestInfo info = (LoginRequestInfo) msg.obj;
+ mContentsClient.onReceivedLoginRequest(info.mRealm, info.mAccount, info.mArgs);
+ break;
+ }
+ case MSG_ON_RECEIVED_ERROR: {
+ OnReceivedErrorInfo info = (OnReceivedErrorInfo) msg.obj;
+ mContentsClient.onReceivedError(info.mErrorCode, info.mDescription,
+ info.mFailingUrl);
+ break;
+ }
+ default:
+ throw new IllegalStateException(
+ "AwContentsClientCallbackHelper: unhandled message " + msg.what);
+ }
+ }
+ };
+
+ public AwContentsClientCallbackHelper(AwContentsClient contentsClient) {
+ mContentsClient = contentsClient;
+ }
+
+ public void postOnLoadResource(String url) {
+ mHandler.sendMessage(mHandler.obtainMessage(MSG_ON_LOAD_RESOURCE, url));
+ }
+
+ public void postOnPageStarted(String url) {
+ mHandler.sendMessage(mHandler.obtainMessage(MSG_ON_PAGE_STARTED, url));
+ }
+
+ public void postOnDownloadStart(String url, String userAgent, String contentDisposition,
+ String mimeType, long contentLength) {
+ DownloadInfo info = new DownloadInfo(url, userAgent, contentDisposition, mimeType,
+ contentLength);
+ mHandler.sendMessage(mHandler.obtainMessage(MSG_ON_DOWNLOAD_START, info));
+ }
+
+ public void postOnReceivedLoginRequest(String realm, String account, String args) {
+ LoginRequestInfo info = new LoginRequestInfo(realm, account, args);
+ mHandler.sendMessage(mHandler.obtainMessage(MSG_ON_RECEIVED_LOGIN_REQUEST, info));
+ }
+
+ public void postOnReceivedError(int errorCode, String description, String failingUrl) {
+ OnReceivedErrorInfo info = new OnReceivedErrorInfo(errorCode, description, failingUrl);
+ mHandler.sendMessage(mHandler.obtainMessage(MSG_ON_RECEIVED_ERROR, info));
+ }
+}
diff --git a/src/org/chromium/android_webview/AwContentsIoThreadClient.java b/src/org/chromium/android_webview/AwContentsIoThreadClient.java
new file mode 100644
index 0000000..3d3dd61
--- /dev/null
+++ b/src/org/chromium/android_webview/AwContentsIoThreadClient.java
@@ -0,0 +1,42 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+package org.chromium.android_webview;
+
+import org.chromium.base.CalledByNative;
+import org.chromium.base.JNINamespace;
+
+/**
+ * Delegate for handling callbacks. All methods are called on the IO thread.
+ *
+ * You should create a separate instance for every WebContents that requires the
+ * provided functionality.
+ */
+@JNINamespace("android_webview")
+public interface AwContentsIoThreadClient {
+ @CalledByNative
+ public int getCacheMode();
+
+ @CalledByNative
+ public InterceptedRequestData shouldInterceptRequest(String url, boolean isMainFrame);
+
+ @CalledByNative
+ public boolean shouldBlockContentUrls();
+
+ @CalledByNative
+ public boolean shouldBlockFileUrls();
+
+ @CalledByNative
+ public boolean shouldBlockNetworkLoads();
+
+ @CalledByNative
+ public void onDownloadStart(String url,
+ String userAgent,
+ String contentDisposition,
+ String mimeType,
+ long contentLength);
+
+ @CalledByNative
+ public void newLoginRequest(String realm, String account, String args);
+}
diff --git a/src/org/chromium/android_webview/AwCookieManager.java b/src/org/chromium/android_webview/AwCookieManager.java
new file mode 100644
index 0000000..53db5d8
--- /dev/null
+++ b/src/org/chromium/android_webview/AwCookieManager.java
@@ -0,0 +1,128 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+package org.chromium.android_webview;
+
+import android.net.ParseException;
+import android.util.Log;
+
+import org.chromium.base.JNINamespace;
+import org.chromium.base.ThreadUtils;
+
+import java.util.concurrent.Callable;
+
+/**
+ * AwCookieManager manages cookies according to RFC2109 spec.
+ *
+ * Methods in this class are thread safe.
+ */
+@JNINamespace("android_webview")
+public final class AwCookieManager {
+ /**
+ * Control whether cookie is enabled or disabled
+ * @param accept TRUE if accept cookie
+ */
+ public void setAcceptCookie(boolean accept) {
+ nativeSetAcceptCookie(accept);
+ }
+
+ /**
+ * Return whether cookie is enabled
+ * @return TRUE if accept cookie
+ */
+ public boolean acceptCookie() {
+ return nativeAcceptCookie();
+ }
+
+ /**
+ * Set cookie for a given url. The old cookie with same host/path/name will
+ * be removed. The new cookie will be added if it is not expired or it does
+ * not have expiration which implies it is session cookie.
+ * @param url The url which cookie is set for
+ * @param value The value for set-cookie: in http response header
+ */
+ public void setCookie(final String url, final String value) {
+ nativeSetCookie(url, value);
+ }
+
+ /**
+ * Get cookie(s) for a given url so that it can be set to "cookie:" in http
+ * request header.
+ * @param url The url needs cookie
+ * @return The cookies in the format of NAME=VALUE [; NAME=VALUE]
+ */
+ public String getCookie(final String url) {
+ String cookie = nativeGetCookie(url.toString());
+ // Return null if the string is empty to match legacy behavior
+ return cookie == null || cookie.trim().isEmpty() ? null : cookie;
+ }
+
+ /**
+ * Remove all session cookies, which are cookies without expiration date
+ */
+ public void removeSessionCookie() {
+ nativeRemoveSessionCookie();
+ }
+
+ /**
+ * Remove all cookies
+ */
+ public void removeAllCookie() {
+ nativeRemoveAllCookie();
+ }
+
+ /**
+ * Return true if there are stored cookies.
+ */
+ public boolean hasCookies() {
+ return nativeHasCookies();
+ }
+
+ /**
+ * Remove all expired cookies
+ */
+ public void removeExpiredCookie() {
+ nativeRemoveExpiredCookie();
+ }
+
+ public void flushCookieStore() {
+ nativeFlushCookieStore();
+ }
+
+ /**
+ * Whether cookies are accepted for file scheme URLs.
+ */
+ public boolean allowFileSchemeCookies() {
+ return nativeAllowFileSchemeCookies();
+ }
+
+ /**
+ * Sets whether cookies are accepted for file scheme URLs.
+ *
+ * Use of cookies with file scheme URLs is potentially insecure. Do not use this feature unless
+ * you can be sure that no unintentional sharing of cookie data can take place.
+ *
+ * Note that calls to this method will have no effect if made after a WebView or CookieManager
+ * instance has been created.
+ */
+ public void setAcceptFileSchemeCookies(boolean accept) {
+ nativeSetAcceptFileSchemeCookies(accept);
+ }
+
+ private native void nativeSetAcceptCookie(boolean accept);
+ private native boolean nativeAcceptCookie();
+
+ private native void nativeSetCookie(String url, String value);
+ private native String nativeGetCookie(String url);
+
+ private native void nativeRemoveSessionCookie();
+ private native void nativeRemoveAllCookie();
+ private native void nativeRemoveExpiredCookie();
+ private native void nativeFlushCookieStore();
+
+ private native boolean nativeHasCookies();
+
+ private native boolean nativeAllowFileSchemeCookies();
+ private native void nativeSetAcceptFileSchemeCookies(boolean accept);
+}
diff --git a/src/org/chromium/android_webview/AwFormDatabase.java b/src/org/chromium/android_webview/AwFormDatabase.java
new file mode 100644
index 0000000..9687a1c
--- /dev/null
+++ b/src/org/chromium/android_webview/AwFormDatabase.java
@@ -0,0 +1,30 @@
+// Copyright 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+package org.chromium.android_webview;
+
+import org.chromium.base.JNINamespace;
+
+/**
+ * Exposes a subset of Chromium form database to Webview database for managing autocomplete
+ * functionality.
+ */
+@JNINamespace("android_webview")
+public class AwFormDatabase {
+
+ public static boolean hasFormData() {
+ return nativeHasFormData();
+ }
+
+ public static void clearFormData() {
+ nativeClearFormData();
+ }
+
+ //--------------------------------------------------------------------------------------------
+ // Native methods
+ //--------------------------------------------------------------------------------------------
+ private static native boolean nativeHasFormData();
+
+ private static native void nativeClearFormData();
+}
diff --git a/src/org/chromium/android_webview/AwGeolocationPermissions.java b/src/org/chromium/android_webview/AwGeolocationPermissions.java
new file mode 100644
index 0000000..b091b3b
--- /dev/null
+++ b/src/org/chromium/android_webview/AwGeolocationPermissions.java
@@ -0,0 +1,135 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+package org.chromium.android_webview;
+
+import android.content.SharedPreferences;
+import android.webkit.ValueCallback;
+
+import org.chromium.base.ThreadUtils;
+import org.chromium.net.GURLUtils;
+
+import java.util.HashSet;
+import java.util.Set;
+
+/**
+ * This class is used to manage permissions for the WebView's Geolocation JavaScript API.
+ *
+ * Callbacks are posted on the UI thread.
+ */
+public final class AwGeolocationPermissions {
+
+ private static final String PREF_PREFIX =
+ AwGeolocationPermissions.class.getCanonicalName() + "%";
+ private final SharedPreferences mSharedPreferences;
+
+ public AwGeolocationPermissions(SharedPreferences sharedPreferences) {
+ mSharedPreferences = sharedPreferences;
+ }
+
+ /**
+ * Set one origin to be allowed.
+ */
+ public void allow(String origin) {
+ String key = getOriginKey(origin);
+ if (key != null) {
+ mSharedPreferences.edit().putBoolean(key, true).apply();
+ }
+ }
+
+ /**
+ * Set one origin to be denied.
+ */
+ public void deny(String origin) {
+ String key = getOriginKey(origin);
+ if (key != null) {
+ mSharedPreferences.edit().putBoolean(key, false).apply();
+ }
+ }
+
+ /**
+ * Clear the stored permission for a particular origin.
+ */
+ public void clear(String origin) {
+ String key = getOriginKey(origin);
+ if (key != null) {
+ mSharedPreferences.edit().remove(key).apply();
+ }
+ }
+
+ /**
+ * Clear stored permissions for all origins.
+ */
+ public void clearAll() {
+ SharedPreferences.Editor editor = null;
+ for (String name : mSharedPreferences.getAll().keySet()) {
+ if (name.startsWith(PREF_PREFIX)) {
+ if (editor == null) {
+ editor = mSharedPreferences.edit();
+ }
+ editor.remove(name);
+ }
+ }
+ if (editor != null) {
+ editor.apply();
+ }
+ }
+
+ /**
+ * Synchronous method to get if an origin is set to be allowed.
+ */
+ public boolean isOriginAllowed(String origin) {
+ return mSharedPreferences.getBoolean(getOriginKey(origin), false);
+ }
+
+ /**
+ * Returns true if the origin is either set to allowed or denied.
+ */
+ public boolean hasOrigin(String origin) {
+ return mSharedPreferences.contains(getOriginKey(origin));
+ }
+
+ /**
+ * Asynchronous method to get if an origin set to be allowed.
+ */
+ public void getAllowed(String origin, final ValueCallback callback) {
+ final boolean finalAllowed = isOriginAllowed(origin);
+ ThreadUtils.postOnUiThread(new Runnable() {
+ @Override
+ public void run() {
+ callback.onReceiveValue(finalAllowed);
+ }
+ });
+ }
+
+ /**
+ * Async method to get the domains currently allowed or denied.
+ */
+ public void getOrigins(final ValueCallback> callback) {
+ final Set origins = new HashSet();
+ for (String name : mSharedPreferences.getAll().keySet()) {
+ if (name.startsWith(PREF_PREFIX)) {
+ origins.add(name.substring(PREF_PREFIX.length()));
+ }
+ }
+ ThreadUtils.postOnUiThread(new Runnable() {
+ @Override
+ public void run() {
+ callback.onReceiveValue(origins);
+ }
+ });
+ }
+
+ /**
+ * Get the domain of an URL using the GURL library.
+ */
+ private String getOriginKey(String url) {
+ String origin = GURLUtils.getOrigin(url);
+ if (origin.isEmpty()) {
+ return null;
+ }
+
+ return PREF_PREFIX + origin;
+ }
+}
diff --git a/src/org/chromium/android_webview/AwHttpAuthHandler.java b/src/org/chromium/android_webview/AwHttpAuthHandler.java
new file mode 100644
index 0000000..5e5a9f1
--- /dev/null
+++ b/src/org/chromium/android_webview/AwHttpAuthHandler.java
@@ -0,0 +1,52 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+package org.chromium.android_webview;
+
+import org.chromium.base.CalledByNative;
+import org.chromium.base.JNINamespace;
+
+@JNINamespace("android_webview")
+public class AwHttpAuthHandler {
+
+ private int mNativeAwHttpAuthHandler;
+ private final boolean mFirstAttempt;
+
+ public void proceed(String username, String password) {
+ if (mNativeAwHttpAuthHandler != 0) {
+ nativeProceed(mNativeAwHttpAuthHandler, username, password);
+ mNativeAwHttpAuthHandler = 0;
+ }
+ }
+
+ public void cancel() {
+ if (mNativeAwHttpAuthHandler != 0) {
+ nativeCancel(mNativeAwHttpAuthHandler);
+ mNativeAwHttpAuthHandler = 0;
+ }
+ }
+
+ public boolean isFirstAttempt() {
+ return mFirstAttempt;
+ }
+
+ @CalledByNative
+ public static AwHttpAuthHandler create(int nativeAwAuthHandler, boolean firstAttempt) {
+ return new AwHttpAuthHandler(nativeAwAuthHandler, firstAttempt);
+ }
+
+ private AwHttpAuthHandler(int nativeAwHttpAuthHandler, boolean firstAttempt) {
+ mNativeAwHttpAuthHandler = nativeAwHttpAuthHandler;
+ mFirstAttempt = firstAttempt;
+ }
+
+ @CalledByNative
+ void handlerDestroyed() {
+ mNativeAwHttpAuthHandler = 0;
+ }
+
+ private native void nativeProceed(int nativeAwHttpAuthHandler,
+ String username, String password);
+ private native void nativeCancel(int nativeAwHttpAuthHandler);
+}
diff --git a/src/org/chromium/android_webview/AwLayoutSizer.java b/src/org/chromium/android_webview/AwLayoutSizer.java
new file mode 100644
index 0000000..7a48bfe
--- /dev/null
+++ b/src/org/chromium/android_webview/AwLayoutSizer.java
@@ -0,0 +1,178 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+package org.chromium.android_webview;
+
+import android.util.Pair;
+import android.view.View.MeasureSpec;
+import android.view.View;
+
+import org.chromium.content.browser.ContentViewCore;
+
+/**
+ * Helper methods used to manage the layout of the View that contains AwContents.
+ */
+public class AwLayoutSizer {
+ // These are used to prevent a re-layout if the content size changes within a dimension that is
+ // fixed by the view system.
+ private boolean mWidthMeasurementIsFixed;
+ private boolean mHeightMeasurementIsFixed;
+
+ // Size of the rendered content, as reported by native.
+ private int mContentHeightCss;
+ private int mContentWidthCss;
+
+ // Page scale factor. This is set to zero initially so that we don't attempt to do a layout if
+ // we get the content size change notification first and a page scale change second.
+ private double mPageScaleFactor = 0.0;
+
+ // Whether to postpone layout requests.
+ private boolean mFreezeLayoutRequests;
+ // Did we try to request a layout since the last time mPostponeLayoutRequests was set to true.
+ private boolean mFrozenLayoutRequestPending;
+
+ private double mDIPScale;
+
+ // Callback object for interacting with the View.
+ private Delegate mDelegate;
+
+ public interface Delegate {
+ void requestLayout();
+ void setMeasuredDimension(int measuredWidth, int measuredHeight);
+ }
+
+ /**
+ * Default constructor. Note: both setDelegate and setDIPScale must be called before the class
+ * is ready for use.
+ */
+ public AwLayoutSizer() {
+ }
+
+ public void setDelegate(Delegate delegate) {
+ mDelegate = delegate;
+ }
+
+ public void setDIPScale(double dipScale) {
+ mDIPScale = dipScale;
+ }
+
+ /**
+ * This is used to register the AwLayoutSizer to preferred content size change notifications in
+ * the AwWebContentsDelegate.
+ */
+ public AwWebContentsDelegateAdapter.PreferredSizeChangedListener
+ getPreferredSizeChangedListener() {
+ return new AwWebContentsDelegateAdapter.PreferredSizeChangedListener() {
+ @Override
+ public void updatePreferredSize(int widthCss, int heightCss) {
+ onContentSizeChanged(widthCss, heightCss);
+ }
+ };
+ }
+
+ /**
+ * Postpone requesting layouts till unfreezeLayoutRequests is called.
+ */
+ public void freezeLayoutRequests() {
+ mFreezeLayoutRequests = true;
+ mFrozenLayoutRequestPending = false;
+ }
+
+ /**
+ * Stop postponing layout requests and request layout if such a request would have been made
+ * had the freezeLayoutRequests method not been called before.
+ */
+ public void unfreezeLayoutRequests() {
+ mFreezeLayoutRequests = false;
+ if (mFrozenLayoutRequestPending) {
+ mFrozenLayoutRequestPending = false;
+ mDelegate.requestLayout();
+ }
+ }
+
+ /**
+ * Update the contents size.
+ * This should be called whenever the content size changes (due to DOM manipulation or page
+ * load, for example).
+ * The width and height should be in CSS pixels.
+ */
+ public void onContentSizeChanged(int widthCss, int heightCss) {
+ doUpdate(widthCss, heightCss, mPageScaleFactor);
+ }
+
+ /**
+ * Update the contents page scale.
+ * This should be called whenever the content page scale factor changes (due to pinch zoom, for
+ * example).
+ */
+ public void onPageScaleChanged(double pageScaleFactor) {
+ doUpdate(mContentWidthCss, mContentHeightCss, pageScaleFactor);
+ }
+
+ private void doUpdate(int widthCss, int heightCss, double pageScaleFactor) {
+ // We want to request layout only if the size or scale change, however if any of the
+ // measurements are 'fixed', then changing the underlying size won't have any effect, so we
+ // ignore changes to dimensions that are 'fixed'.
+ boolean anyMeasurementNotFixed = !mWidthMeasurementIsFixed || !mHeightMeasurementIsFixed;
+ boolean layoutNeeded = (mContentWidthCss != widthCss && !mWidthMeasurementIsFixed) ||
+ (mContentHeightCss != heightCss && !mHeightMeasurementIsFixed) ||
+ (mPageScaleFactor != pageScaleFactor && anyMeasurementNotFixed);
+
+ mContentWidthCss = widthCss;
+ mContentHeightCss = heightCss;
+ mPageScaleFactor = pageScaleFactor;
+
+ if (layoutNeeded) {
+ if (mFreezeLayoutRequests) {
+ mFrozenLayoutRequestPending = true;
+ } else {
+ mDelegate.requestLayout();
+ }
+ }
+ }
+
+ /**
+ * Calculate the size of the view.
+ * This is designed to be used to implement the android.view.View#onMeasure() method.
+ */
+ public void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
+ int heightMode = MeasureSpec.getMode(heightMeasureSpec);
+ int heightSize = MeasureSpec.getSize(heightMeasureSpec);
+ int widthMode = MeasureSpec.getMode(widthMeasureSpec);
+ int widthSize = MeasureSpec.getSize(widthMeasureSpec);
+
+ int measuredHeight = heightSize;
+ int measuredWidth = widthSize;
+
+ int contentHeightPix = (int) (mContentHeightCss * mPageScaleFactor * mDIPScale);
+ int contentWidthPix = (int) (mContentWidthCss * mPageScaleFactor * mDIPScale);
+
+ // Always use the given size unless unspecified. This matches WebViewClassic behavior.
+ mWidthMeasurementIsFixed = (widthMode != MeasureSpec.UNSPECIFIED);
+ // Freeze the height if an exact size is given by the parent or if the content size has
+ // exceeded the maximum size specified by the parent.
+ // TODO(mkosiba): Actually we'd like the reduction in content size to cause the WebView to
+ // shrink back again but only as a result of a page load.
+ mHeightMeasurementIsFixed = (heightMode == MeasureSpec.EXACTLY) ||
+ (heightMode == MeasureSpec.AT_MOST && contentHeightPix > heightSize);
+
+ if (!mHeightMeasurementIsFixed) {
+ measuredHeight = contentHeightPix;
+ }
+
+ if (!mWidthMeasurementIsFixed) {
+ measuredWidth = contentWidthPix;
+ }
+
+ if (measuredHeight < contentHeightPix) {
+ measuredHeight |= View.MEASURED_STATE_TOO_SMALL;
+ }
+
+ if (measuredWidth < contentWidthPix) {
+ measuredWidth |= View.MEASURED_STATE_TOO_SMALL;
+ }
+
+ mDelegate.setMeasuredDimension(measuredWidth, measuredHeight);
+ }
+}
diff --git a/src/org/chromium/android_webview/AwQuotaManagerBridge.java b/src/org/chromium/android_webview/AwQuotaManagerBridge.java
new file mode 100644
index 0000000..6e94134
--- /dev/null
+++ b/src/org/chromium/android_webview/AwQuotaManagerBridge.java
@@ -0,0 +1,162 @@
+// Copyright (c) 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+package org.chromium.android_webview;
+
+import org.chromium.base.CalledByNative;
+import org.chromium.base.JNINamespace;
+import org.chromium.base.ThreadUtils;
+
+import android.webkit.ValueCallback;
+
+import java.util.Map;
+import java.util.HashMap;
+
+/**
+ * Bridge between android.webview.WebStorage and native QuotaManager. This object is owned by Java
+ * AwBrowserContext and the native side is owned by the native AwBrowserContext.
+ *
+ * TODO(boliu): Actually make this true after Java AwBrowserContext is added.
+ */
+@JNINamespace("android_webview")
+public class AwQuotaManagerBridge {
+ // TODO(boliu): This should be obtained from Java AwBrowserContext that owns this.
+ private static native int nativeGetDefaultNativeAwQuotaManagerBridge();
+
+ // TODO(boliu): This should be owned by Java AwBrowserContext, not a singleton.
+ private static AwQuotaManagerBridge sInstance;
+ public static AwQuotaManagerBridge getInstance() {
+ ThreadUtils.assertOnUiThread();
+ if (sInstance == null) {
+ sInstance = new AwQuotaManagerBridge(nativeGetDefaultNativeAwQuotaManagerBridge());
+ }
+ return sInstance;
+ }
+
+ /**
+ * This class represent the callback value of android.webview.WebStorage.getOrigins. The values
+ * are optimized for JNI convenience and need to be converted.
+ */
+ public static class Origins {
+ // Origin, usage, and quota data in parallel arrays of same length.
+ public final String[] mOrigins;
+ public final long[] mUsages;
+ public final long[] mQuotas;
+
+ Origins(String[] origins, long[] usages, long[] quotas) {
+ mOrigins = origins;
+ mUsages = usages;
+ mQuotas = quotas;
+ }
+ }
+
+ // This is not owning. The native object is owned by the native AwBrowserContext.
+ private int mNativeAwQuotaManagerBridgeImpl;
+
+ // The Java callbacks are saved here. An incrementing callback id is generated for each saved
+ // callback and is passed to the native side to identify callback.
+ private int mNextId;
+ private Map> mPendingGetOriginCallbacks;
+ private Map> mPendingGetQuotaForOriginCallbacks;
+ private Map> mPendingGetUsageForOriginCallbacks;
+
+ private AwQuotaManagerBridge(int nativeAwQuotaManagerBridgeImpl) {
+ mNativeAwQuotaManagerBridgeImpl = nativeAwQuotaManagerBridgeImpl;
+ mPendingGetOriginCallbacks =
+ new HashMap>();
+ mPendingGetQuotaForOriginCallbacks = new HashMap>();
+ mPendingGetUsageForOriginCallbacks = new HashMap>();
+ nativeInit(mNativeAwQuotaManagerBridgeImpl);
+ }
+
+ private int getNextId() {
+ ThreadUtils.assertOnUiThread();
+ return ++mNextId;
+ }
+
+ /*
+ * There are five HTML5 offline storage APIs.
+ * 1) Web Storage (ie the localStorage and sessionStorage variables)
+ * 2) Web SQL database
+ * 3) Application cache
+ * 4) Indexed Database
+ * 5) Filesystem API
+ */
+
+ /**
+ * Implements WebStorage.deleteAllData(). Clear the storage of all five offline APIs.
+ *
+ * TODO(boliu): Actually clear Web Storage.
+ */
+ public void deleteAllData() {
+ nativeDeleteAllData(mNativeAwQuotaManagerBridgeImpl);
+ }
+
+ /**
+ * Implements WebStorage.deleteOrigin(). Clear the storage of APIs 2-5 for the given origin.
+ */
+ public void deleteOrigin(String origin) {
+ nativeDeleteOrigin(mNativeAwQuotaManagerBridgeImpl, origin);
+ }
+
+ /**
+ * Implements WebStorage.getOrigins. Get the per origin usage and quota of APIs 2-5 in
+ * aggregate.
+ */
+ public void getOrigins(ValueCallback callback) {
+ int callbackId = getNextId();
+ assert !mPendingGetOriginCallbacks.containsKey(callbackId);
+ mPendingGetOriginCallbacks.put(callbackId, callback);
+ nativeGetOrigins(mNativeAwQuotaManagerBridgeImpl, callbackId);
+ }
+
+ /**
+ * Implements WebStorage.getQuotaForOrigin. Get the quota of APIs 2-5 in aggregate for given
+ * origin.
+ */
+ public void getQuotaForOrigin(String origin, ValueCallback callback) {
+ int callbackId = getNextId();
+ assert !mPendingGetQuotaForOriginCallbacks.containsKey(callbackId);
+ mPendingGetQuotaForOriginCallbacks.put(callbackId, callback);
+ nativeGetUsageAndQuotaForOrigin(mNativeAwQuotaManagerBridgeImpl, origin, callbackId, true);
+ }
+
+ /**
+ * Implements WebStorage.getUsageForOrigin. Get the usage of APIs 2-5 in aggregate for given
+ * origin.
+ */
+ public void getUsageForOrigin(String origin, ValueCallback callback) {
+ int callbackId = getNextId();
+ assert !mPendingGetUsageForOriginCallbacks.containsKey(callbackId);
+ mPendingGetUsageForOriginCallbacks.put(callbackId, callback);
+ nativeGetUsageAndQuotaForOrigin(mNativeAwQuotaManagerBridgeImpl, origin, callbackId, false);
+ }
+
+ @CalledByNative
+ private void onGetOriginsCallback(int callbackId, String[] origin, long[] usages,
+ long[] quotas) {
+ assert mPendingGetOriginCallbacks.containsKey(callbackId);
+ mPendingGetOriginCallbacks.remove(callbackId).onReceiveValue(
+ new Origins(origin, usages, quotas));
+ }
+
+ @CalledByNative
+ private void onGetUsageAndQuotaForOriginCallback(
+ int callbackId, boolean isQuota, long usage, long quota) {
+ if (isQuota) {
+ assert mPendingGetQuotaForOriginCallbacks.containsKey(callbackId);
+ mPendingGetQuotaForOriginCallbacks.remove(callbackId).onReceiveValue(quota);
+ } else {
+ assert mPendingGetUsageForOriginCallbacks.containsKey(callbackId);
+ mPendingGetUsageForOriginCallbacks.remove(callbackId).onReceiveValue(usage);
+ }
+ }
+
+ private native void nativeInit(int nativeAwQuotaManagerBridgeImpl);
+ private native void nativeDeleteAllData(int nativeAwQuotaManagerBridgeImpl);
+ private native void nativeDeleteOrigin(int nativeAwQuotaManagerBridgeImpl, String origin);
+ private native void nativeGetOrigins(int nativeAwQuotaManagerBridgeImpl, int callbackId);
+ private native void nativeGetUsageAndQuotaForOrigin(int nativeAwQuotaManagerBridgeImpl,
+ String origin, int callbackId, boolean isQuota);
+}
diff --git a/src/org/chromium/android_webview/AwResource.java b/src/org/chromium/android_webview/AwResource.java
new file mode 100644
index 0000000..e98be0b
--- /dev/null
+++ b/src/org/chromium/android_webview/AwResource.java
@@ -0,0 +1,118 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+package org.chromium.android_webview;
+
+import org.chromium.base.CalledByNative;
+import org.chromium.base.JNINamespace;
+
+import android.content.res.Resources;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.lang.ref.SoftReference;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.NoSuchElementException;
+import java.util.Scanner;
+
+@JNINamespace("android_webview::AwResource")
+public class AwResource {
+ // The following resource ID's must be initialized by the embedder.
+
+ // Raw resource ID for an HTML page to be displayed in the case of
+ // a specific load error.
+ public static int RAW_LOAD_ERROR;
+
+ // Raw resource ID for an HTML page to be displayed in the case of
+ // a generic load error. (It's called NO_DOMAIN for legacy reasons).
+ public static int RAW_NO_DOMAIN;
+
+ // String resource ID for the default text encoding to use.
+ public static int STRING_DEFAULT_TEXT_ENCODING;
+
+ // The embedder should inject a Resources object that will be used
+ // to resolve Resource IDs into the actual resources.
+ private static Resources sResources;
+
+ // Loading some resources is expensive, so cache the results.
+ private static Map > sResourceCache;
+
+ private static final int TYPE_STRING = 0;
+ private static final int TYPE_RAW = 1;
+
+ public static void setResources(Resources resources) {
+ sResources = resources;
+ sResourceCache = new HashMap >();
+ }
+
+ @CalledByNative
+ public static String getDefaultTextEncoding() {
+ return getResource(STRING_DEFAULT_TEXT_ENCODING, TYPE_STRING);
+ }
+
+ @CalledByNative
+ public static String getNoDomainPageContent() {
+ return getResource(RAW_NO_DOMAIN, TYPE_RAW);
+ }
+
+ @CalledByNative
+ public static String getLoadErrorPageContent() {
+ return getResource(RAW_LOAD_ERROR, TYPE_RAW);
+ }
+
+ private static String getResource(int resid, int type) {
+ assert resid != 0;
+ assert sResources != null;
+ assert sResourceCache != null;
+
+ String result = sResourceCache.get(resid) == null ?
+ null : sResourceCache.get(resid).get();
+ if (result == null) {
+ switch (type) {
+ case TYPE_STRING:
+ result = sResources.getString(resid);
+ break;
+ case TYPE_RAW:
+ result = getRawFileResourceContent(resid);
+ break;
+ default:
+ throw new IllegalArgumentException("Unknown resource type");
+ }
+
+ sResourceCache.put(resid, new SoftReference(result));
+ }
+ return result;
+ }
+
+ private static String getRawFileResourceContent(int resid) {
+ assert resid != 0;
+ assert sResources != null;
+
+ InputStreamReader isr = null;
+ String result = null;
+
+ try {
+ isr = new InputStreamReader(
+ sResources.openRawResource(resid));
+ // \A tells the scanner to use the beginning of the input
+ // as the delimiter, hence causes it to read the entire text.
+ result = new Scanner(isr).useDelimiter("\\A").next();
+ } catch (Resources.NotFoundException e) {
+ return "";
+ } catch (NoSuchElementException e) {
+ return "";
+ }
+ finally {
+ try {
+ if (isr != null) {
+ isr.close();
+ }
+ } catch(IOException e) {
+ }
+ }
+ return result;
+ }
+}
diff --git a/src/org/chromium/android_webview/AwSettings.java b/src/org/chromium/android_webview/AwSettings.java
new file mode 100644
index 0000000..a578116
--- /dev/null
+++ b/src/org/chromium/android_webview/AwSettings.java
@@ -0,0 +1,1371 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+package org.chromium.android_webview;
+
+import android.content.Context;
+import android.content.pm.PackageManager;
+import android.os.Handler;
+import android.os.Looper;
+import android.os.Message;
+import android.os.Process;
+import android.webkit.WebSettings.PluginState;
+import android.webkit.WebSettings;
+import android.webkit.WebView;
+
+import org.chromium.base.CalledByNative;
+import org.chromium.base.JNINamespace;
+import org.chromium.base.ThreadUtils;
+import org.chromium.content.browser.ContentViewCore;
+
+/**
+ * Stores Android WebView specific settings that does not need to be synced to WebKit.
+ * Use {@link org.chromium.content.browser.ContentSettings} for WebKit settings.
+ *
+ * Methods in this class can be called from any thread, including threads created by
+ * the client of WebView.
+ */
+@JNINamespace("android_webview")
+public class AwSettings {
+ // This enum corresponds to WebSettings.LayoutAlgorithm. We use our own to be
+ // able to extend it.
+ public enum LayoutAlgorithm {
+ NORMAL,
+ SINGLE_COLUMN,
+ NARROW_COLUMNS,
+ TEXT_AUTOSIZING,
+ }
+
+ private static final String TAG = "AwSettings";
+
+ // This class must be created on the UI thread. Afterwards, it can be
+ // used from any thread. Internally, the class uses a message queue
+ // to call native code on the UI thread only.
+
+ // Lock to protect all settings.
+ private final Object mAwSettingsLock = new Object();
+
+ private final Context mContext;
+ private double mDIPScale;
+
+ private LayoutAlgorithm mLayoutAlgorithm = LayoutAlgorithm.NARROW_COLUMNS;
+ private int mTextSizePercent = 100;
+ private String mStandardFontFamily = "sans-serif";
+ private String mFixedFontFamily = "monospace";
+ private String mSansSerifFontFamily = "sans-serif";
+ private String mSerifFontFamily = "serif";
+ private String mCursiveFontFamily = "cursive";
+ private String mFantasyFontFamily = "fantasy";
+ // TODO(mnaganov): Should be obtained from Android. Problem: it is hidden.
+ private String mDefaultTextEncoding = "Latin-1";
+ private String mUserAgent;
+ private int mMinimumFontSize = 8;
+ private int mMinimumLogicalFontSize = 8;
+ private int mDefaultFontSize = 16;
+ private int mDefaultFixedFontSize = 13;
+ private boolean mLoadsImagesAutomatically = true;
+ private boolean mImagesEnabled = true;
+ private boolean mJavaScriptEnabled = false;
+ private boolean mAllowUniversalAccessFromFileURLs = false;
+ private boolean mAllowFileAccessFromFileURLs = false;
+ private boolean mJavaScriptCanOpenWindowsAutomatically = false;
+ private boolean mSupportMultipleWindows = false;
+ private PluginState mPluginState = PluginState.OFF;
+ private boolean mAppCacheEnabled = false;
+ private boolean mDomStorageEnabled = false;
+ private boolean mDatabaseEnabled = false;
+ private boolean mUseWideViewport = false;
+ private boolean mLoadWithOverviewMode = false;
+ private boolean mMediaPlaybackRequiresUserGesture = true;
+ private String mDefaultVideoPosterURL;
+ private float mInitialPageScalePercent = 0;
+
+ private final boolean mSupportDeprecatedTargetDensityDPI = true;
+
+ // Not accessed by the native side.
+ private boolean mBlockNetworkLoads; // Default depends on permission of embedding APK.
+ private boolean mAllowContentUrlAccess = true;
+ private boolean mAllowFileUrlAccess = true;
+ private int mCacheMode = WebSettings.LOAD_DEFAULT;
+ private boolean mShouldFocusFirstNode = true;
+ private boolean mGeolocationEnabled = true;
+ private boolean mAutoCompleteEnabled = true;
+ private boolean mSupportZoom = true;
+ private boolean mBuiltInZoomControls = false;
+ private boolean mDisplayZoomControls = true;
+ static class LazyDefaultUserAgent{
+ // Lazy Holder pattern
+ private static final String sInstance = nativeGetDefaultUserAgent();
+ }
+
+ // Protects access to settings global fields.
+ private static final Object sGlobalContentSettingsLock = new Object();
+ // For compatibility with the legacy WebView, we can only enable AppCache when the path is
+ // provided. However, we don't use the path, so we just check if we have received it from the
+ // client.
+ private static boolean sAppCachePathIsSet = false;
+
+ // The native side of this object.
+ private int mNativeAwSettings = 0;
+
+ private ContentViewCore mContentViewCore;
+
+ // A flag to avoid sending superfluous synchronization messages.
+ private boolean mIsUpdateWebkitPrefsMessagePending = false;
+ // Custom handler that queues messages to call native code on the UI thread.
+ private final EventHandler mEventHandler;
+
+ private static final int MINIMUM_FONT_SIZE = 1;
+ private static final int MAXIMUM_FONT_SIZE = 72;
+
+ // Class to handle messages to be processed on the UI thread.
+ private class EventHandler {
+ // Message id for updating Webkit preferences
+ private static final int UPDATE_WEBKIT_PREFERENCES = 0;
+ // Actual UI thread handler
+ private Handler mHandler;
+
+ EventHandler() {
+ mHandler = new Handler(Looper.getMainLooper()) {
+ @Override
+ public void handleMessage(Message msg) {
+ switch (msg.what) {
+ case UPDATE_WEBKIT_PREFERENCES:
+ synchronized (mAwSettingsLock) {
+ updateWebkitPreferencesOnUiThreadLocked();
+ mIsUpdateWebkitPrefsMessagePending = false;
+ mAwSettingsLock.notifyAll();
+ }
+ break;
+ }
+ }
+ };
+ }
+
+ private void updateWebkitPreferencesLocked() {
+ assert Thread.holdsLock(mAwSettingsLock);
+ if (mNativeAwSettings == 0) return;
+ if (Looper.myLooper() == mHandler.getLooper()) {
+ updateWebkitPreferencesOnUiThreadLocked();
+ } else {
+ // We're being called on a background thread, so post a message.
+ if (mIsUpdateWebkitPrefsMessagePending) {
+ return;
+ }
+ mIsUpdateWebkitPrefsMessagePending = true;
+ mHandler.sendMessage(Message.obtain(null, UPDATE_WEBKIT_PREFERENCES));
+ // We must block until the settings have been sync'd to native to
+ // ensure that they have taken effect.
+ try {
+ while (mIsUpdateWebkitPrefsMessagePending) {
+ mAwSettingsLock.wait();
+ }
+ } catch (InterruptedException e) {}
+ }
+ }
+ }
+
+ public AwSettings(Context context,
+ int nativeWebContents,
+ ContentViewCore contentViewCore,
+ boolean isAccessFromFileURLsGrantedByDefault) {
+ ThreadUtils.assertOnUiThread();
+ mContext = context;
+ mBlockNetworkLoads = mContext.checkPermission(
+ android.Manifest.permission.INTERNET,
+ Process.myPid(),
+ Process.myUid()) != PackageManager.PERMISSION_GRANTED;
+ mContentViewCore = contentViewCore;
+ mContentViewCore.updateMultiTouchZoomSupport(supportsMultiTouchZoomLocked());
+
+ if (isAccessFromFileURLsGrantedByDefault) {
+ mAllowUniversalAccessFromFileURLs = true;
+ mAllowFileAccessFromFileURLs = true;
+ }
+
+ mEventHandler = new EventHandler();
+ mUserAgent = LazyDefaultUserAgent.sInstance;
+
+ synchronized (mAwSettingsLock) {
+ mNativeAwSettings = nativeInit(nativeWebContents);
+ }
+ assert mNativeAwSettings != 0;
+ }
+
+ public void destroy() {
+ nativeDestroy(mNativeAwSettings);
+ mNativeAwSettings = 0;
+ }
+
+ public void setDIPScale(double dipScale) {
+ synchronized (mAwSettingsLock) {
+ mDIPScale = dipScale;
+ }
+ }
+
+ @CalledByNative
+ private double getDIPScaleLocked() {
+ return mDIPScale;
+ }
+
+ public void setWebContents(int nativeWebContents) {
+ synchronized (mAwSettingsLock) {
+ nativeSetWebContentsLocked(mNativeAwSettings, nativeWebContents);
+ }
+ }
+
+ /**
+ * See {@link android.webkit.WebSettings#setBlockNetworkLoads}.
+ */
+ public void setBlockNetworkLoads(boolean flag) {
+ synchronized (mAwSettingsLock) {
+ if (!flag && mContext.checkPermission(
+ android.Manifest.permission.INTERNET,
+ Process.myPid(),
+ Process.myUid()) != PackageManager.PERMISSION_GRANTED) {
+ throw new SecurityException("Permission denied - " +
+ "application missing INTERNET permission");
+ }
+ mBlockNetworkLoads = flag;
+ }
+ }
+
+ /**
+ * See {@link android.webkit.WebSettings#getBlockNetworkLoads}.
+ */
+ public boolean getBlockNetworkLoads() {
+ synchronized (mAwSettingsLock) {
+ return mBlockNetworkLoads;
+ }
+ }
+
+ /**
+ * See {@link android.webkit.WebSettings#setAllowFileAccess}.
+ */
+ public void setAllowFileAccess(boolean allow) {
+ synchronized (mAwSettingsLock) {
+ if (mAllowFileUrlAccess != allow) {
+ mAllowFileUrlAccess = allow;
+ }
+ }
+ }
+
+ /**
+ * See {@link android.webkit.WebSettings#getAllowFileAccess}.
+ */
+ public boolean getAllowFileAccess() {
+ synchronized (mAwSettingsLock) {
+ return mAllowFileUrlAccess;
+ }
+ }
+
+ /**
+ * See {@link android.webkit.WebSettings#setAllowContentAccess}.
+ */
+ public void setAllowContentAccess(boolean allow) {
+ synchronized (mAwSettingsLock) {
+ if (mAllowContentUrlAccess != allow) {
+ mAllowContentUrlAccess = allow;
+ }
+ }
+ }
+
+ /**
+ * See {@link android.webkit.WebSettings#getAllowContentAccess}.
+ */
+ public boolean getAllowContentAccess() {
+ synchronized (mAwSettingsLock) {
+ return mAllowContentUrlAccess;
+ }
+ }
+
+ /**
+ * See {@link android.webkit.WebSettings#setCacheMode}.
+ */
+ public void setCacheMode(int mode) {
+ synchronized (mAwSettingsLock) {
+ if (mCacheMode != mode) {
+ mCacheMode = mode;
+ }
+ }
+ }
+
+ /**
+ * See {@link android.webkit.WebSettings#getCacheMode}.
+ */
+ public int getCacheMode() {
+ synchronized (mAwSettingsLock) {
+ return mCacheMode;
+ }
+ }
+
+ /**
+ * See {@link android.webkit.WebSettings#setNeedInitialFocus}.
+ */
+ public void setShouldFocusFirstNode(boolean flag) {
+ synchronized (mAwSettingsLock) {
+ mShouldFocusFirstNode = flag;
+ }
+ }
+
+ /**
+ * See {@link android.webkit.WebView#setInitialScale}.
+ */
+ public void setInitialPageScale(final float scaleInPercent) {
+ synchronized (mAwSettingsLock) {
+ if (mInitialPageScalePercent != scaleInPercent) {
+ mInitialPageScalePercent = scaleInPercent;
+ ThreadUtils.runOnUiThreadBlocking(new Runnable() {
+ @Override
+ public void run() {
+ if (mNativeAwSettings != 0) {
+ nativeUpdateInitialPageScaleLocked(mNativeAwSettings);
+ }
+ }
+ });
+ }
+ }
+ }
+
+ @CalledByNative
+ private float getInitialPageScalePercentLocked() {
+ return mInitialPageScalePercent;
+ }
+
+ /**
+ * See {@link android.webkit.WebSettings#setNeedInitialFocus}.
+ */
+ public boolean shouldFocusFirstNode() {
+ synchronized(mAwSettingsLock) {
+ return mShouldFocusFirstNode;
+ }
+ }
+
+ /**
+ * See {@link android.webkit.WebSettings#setGeolocationEnabled}.
+ */
+ public void setGeolocationEnabled(boolean flag) {
+ synchronized (mAwSettingsLock) {
+ if (mGeolocationEnabled != flag) {
+ mGeolocationEnabled = flag;
+ }
+ }
+ }
+
+ /**
+ * @return Returns if geolocation is currently enabled.
+ */
+ boolean getGeolocationEnabled() {
+ synchronized (mAwSettingsLock) {
+ return mGeolocationEnabled;
+ }
+ }
+
+ /**
+ * See {@link android.webkit.WebSettings#setSaveFormData}.
+ */
+ public void setSaveFormData(final boolean enable) {
+ synchronized (mAwSettingsLock) {
+ if (mAutoCompleteEnabled != enable) {
+ mAutoCompleteEnabled = enable;
+ ThreadUtils.runOnUiThreadBlocking(new Runnable() {
+ @Override
+ public void run() {
+ if (mNativeAwSettings != 0) {
+ nativeUpdateFormDataPreferencesLocked(mNativeAwSettings);
+ }
+ }
+ });
+ }
+ }
+ }
+
+ /**
+ * See {@link android.webkit.WebSettings#getSaveFormData}.
+ */
+ public boolean getSaveFormData() {
+ synchronized (mAwSettingsLock) {
+ return getSaveFormDataLocked();
+ }
+ }
+
+ @CalledByNative
+ private boolean getSaveFormDataLocked() {
+ return mAutoCompleteEnabled;
+ }
+
+ /**
+ * @returns the default User-Agent used by each ContentViewCore instance, i.e. unless
+ * overridden by {@link #setUserAgentString()}
+ */
+ public static String getDefaultUserAgent() {
+ return LazyDefaultUserAgent.sInstance;
+ }
+
+ /**
+ * See {@link android.webkit.WebSettings#setUserAgentString}.
+ */
+ public void setUserAgentString(String ua) {
+ synchronized (mAwSettingsLock) {
+ final String oldUserAgent = mUserAgent;
+ if (ua == null || ua.length() == 0) {
+ mUserAgent = LazyDefaultUserAgent.sInstance;
+ } else {
+ mUserAgent = ua;
+ }
+ if (!oldUserAgent.equals(mUserAgent)) {
+ ThreadUtils.runOnUiThreadBlocking(new Runnable() {
+ @Override
+ public void run() {
+ if (mNativeAwSettings != 0) {
+ nativeUpdateUserAgentLocked(mNativeAwSettings);
+ }
+ }
+ });
+ }
+ }
+ }
+
+ /**
+ * See {@link android.webkit.WebSettings#getUserAgentString}.
+ */
+ public String getUserAgentString() {
+ synchronized (mAwSettingsLock) {
+ return mUserAgent;
+ }
+ }
+
+ @CalledByNative
+ private String getUserAgentLocked() {
+ return mUserAgent;
+ }
+
+ /**
+ * See {@link android.webkit.WebSettings#setLoadWithOverviewMode}.
+ */
+ public void setLoadWithOverviewMode(boolean overview) {
+ synchronized (mAwSettingsLock) {
+ if (mLoadWithOverviewMode != overview) {
+ mLoadWithOverviewMode = overview;
+ mEventHandler.updateWebkitPreferencesLocked();
+ ThreadUtils.runOnUiThreadBlocking(new Runnable() {
+ @Override
+ public void run() {
+ if (mNativeAwSettings != 0) {
+ nativeResetScrollAndScaleState(mNativeAwSettings);
+ }
+ }
+ });
+ }
+ }
+ }
+
+ /**
+ * See {@link android.webkit.WebSettings#getLoadWithOverviewMode}.
+ */
+ public boolean getLoadWithOverviewMode() {
+ synchronized (mAwSettingsLock) {
+ return mLoadWithOverviewMode;
+ }
+ }
+
+ @CalledByNative
+ private boolean getLoadWithOverviewModeLocked() {
+ return mLoadWithOverviewMode;
+ }
+
+ /**
+ * See {@link android.webkit.WebSettings#setTextZoom}.
+ */
+ public void setTextZoom(final int textZoom) {
+ synchronized (mAwSettingsLock) {
+ if (mTextSizePercent != textZoom) {
+ mTextSizePercent = textZoom;
+ mEventHandler.updateWebkitPreferencesLocked();
+ }
+ }
+ }
+
+ /**
+ * See {@link android.webkit.WebSettings#getTextZoom}.
+ */
+ public int getTextZoom() {
+ synchronized (mAwSettingsLock) {
+ return mTextSizePercent;
+ }
+ }
+
+ @CalledByNative
+ private int getTextSizePercentLocked() {
+ return mTextSizePercent;
+ }
+
+ /**
+ * See {@link android.webkit.WebSettings#setStandardFontFamily}.
+ */
+ public void setStandardFontFamily(String font) {
+ synchronized (mAwSettingsLock) {
+ if (font != null && !mStandardFontFamily.equals(font)) {
+ mStandardFontFamily = font;
+ mEventHandler.updateWebkitPreferencesLocked();
+ }
+ }
+ }
+
+ /**
+ * See {@link android.webkit.WebSettings#getStandardFontFamily}.
+ */
+ public String getStandardFontFamily() {
+ synchronized (mAwSettingsLock) {
+ return mStandardFontFamily;
+ }
+ }
+
+ @CalledByNative
+ private String getStandardFontFamilyLocked() {
+ return mStandardFontFamily;
+ }
+
+ /**
+ * See {@link android.webkit.WebSettings#setFixedFontFamily}.
+ */
+ public void setFixedFontFamily(String font) {
+ synchronized (mAwSettingsLock) {
+ if (font != null && !mFixedFontFamily.equals(font)) {
+ mFixedFontFamily = font;
+ mEventHandler.updateWebkitPreferencesLocked();
+ }
+ }
+ }
+
+ /**
+ * See {@link android.webkit.WebSettings#getFixedFontFamily}.
+ */
+ public String getFixedFontFamily() {
+ synchronized (mAwSettingsLock) {
+ return mFixedFontFamily;
+ }
+ }
+
+ @CalledByNative
+ private String getFixedFontFamilyLocked() {
+ return mFixedFontFamily;
+ }
+
+ /**
+ * See {@link android.webkit.WebSettings#setSansSerifFontFamily}.
+ */
+ public void setSansSerifFontFamily(String font) {
+ synchronized (mAwSettingsLock) {
+ if (font != null && !mSansSerifFontFamily.equals(font)) {
+ mSansSerifFontFamily = font;
+ mEventHandler.updateWebkitPreferencesLocked();
+ }
+ }
+ }
+
+ /**
+ * See {@link android.webkit.WebSettings#getSansSerifFontFamily}.
+ */
+ public String getSansSerifFontFamily() {
+ synchronized (mAwSettingsLock) {
+ return mSansSerifFontFamily;
+ }
+ }
+
+ @CalledByNative
+ private String getSansSerifFontFamilyLocked() {
+ return mSansSerifFontFamily;
+ }
+
+ /**
+ * See {@link android.webkit.WebSettings#setSerifFontFamily}.
+ */
+ public void setSerifFontFamily(String font) {
+ synchronized (mAwSettingsLock) {
+ if (font != null && !mSerifFontFamily.equals(font)) {
+ mSerifFontFamily = font;
+ mEventHandler.updateWebkitPreferencesLocked();
+ }
+ }
+ }
+
+ /**
+ * See {@link android.webkit.WebSettings#getSerifFontFamily}.
+ */
+ public String getSerifFontFamily() {
+ synchronized (mAwSettingsLock) {
+ return mSerifFontFamily;
+ }
+ }
+
+ @CalledByNative
+ private String getSerifFontFamilyLocked() {
+ return mSerifFontFamily;
+ }
+
+ /**
+ * See {@link android.webkit.WebSettings#setCursiveFontFamily}.
+ */
+ public void setCursiveFontFamily(String font) {
+ synchronized (mAwSettingsLock) {
+ if (font != null && !mCursiveFontFamily.equals(font)) {
+ mCursiveFontFamily = font;
+ mEventHandler.updateWebkitPreferencesLocked();
+ }
+ }
+ }
+
+ /**
+ * See {@link android.webkit.WebSettings#getCursiveFontFamily}.
+ */
+ public String getCursiveFontFamily() {
+ synchronized (mAwSettingsLock) {
+ return mCursiveFontFamily;
+ }
+ }
+
+ @CalledByNative
+ private String getCursiveFontFamilyLocked() {
+ return mCursiveFontFamily;
+ }
+
+ /**
+ * See {@link android.webkit.WebSettings#setFantasyFontFamily}.
+ */
+ public void setFantasyFontFamily(String font) {
+ synchronized (mAwSettingsLock) {
+ if (font != null && !mFantasyFontFamily.equals(font)) {
+ mFantasyFontFamily = font;
+ mEventHandler.updateWebkitPreferencesLocked();
+ }
+ }
+ }
+
+ /**
+ * See {@link android.webkit.WebSettings#getFantasyFontFamily}.
+ */
+ public String getFantasyFontFamily() {
+ synchronized (mAwSettingsLock) {
+ return mFantasyFontFamily;
+ }
+ }
+
+ @CalledByNative
+ private String getFantasyFontFamilyLocked() {
+ return mFantasyFontFamily;
+ }
+
+ /**
+ * See {@link android.webkit.WebSettings#setMinimumFontSize}.
+ */
+ public void setMinimumFontSize(int size) {
+ synchronized (mAwSettingsLock) {
+ size = clipFontSize(size);
+ if (mMinimumFontSize != size) {
+ mMinimumFontSize = size;
+ mEventHandler.updateWebkitPreferencesLocked();
+ }
+ }
+ }
+
+ /**
+ * See {@link android.webkit.WebSettings#getMinimumFontSize}.
+ */
+ public int getMinimumFontSize() {
+ synchronized (mAwSettingsLock) {
+ return mMinimumFontSize;
+ }
+ }
+
+ @CalledByNative
+ private int getMinimumFontSizeLocked() {
+ return mMinimumFontSize;
+ }
+
+ /**
+ * See {@link android.webkit.WebSettings#setMinimumLogicalFontSize}.
+ */
+ public void setMinimumLogicalFontSize(int size) {
+ synchronized (mAwSettingsLock) {
+ size = clipFontSize(size);
+ if (mMinimumLogicalFontSize != size) {
+ mMinimumLogicalFontSize = size;
+ mEventHandler.updateWebkitPreferencesLocked();
+ }
+ }
+ }
+
+ /**
+ * See {@link android.webkit.WebSettings#getMinimumLogicalFontSize}.
+ */
+ public int getMinimumLogicalFontSize() {
+ synchronized (mAwSettingsLock) {
+ return mMinimumLogicalFontSize;
+ }
+ }
+
+ @CalledByNative
+ private int getMinimumLogicalFontSizeLocked() {
+ return mMinimumLogicalFontSize;
+ }
+
+ /**
+ * See {@link android.webkit.WebSettings#setDefaultFontSize}.
+ */
+ public void setDefaultFontSize(int size) {
+ synchronized (mAwSettingsLock) {
+ size = clipFontSize(size);
+ if (mDefaultFontSize != size) {
+ mDefaultFontSize = size;
+ mEventHandler.updateWebkitPreferencesLocked();
+ }
+ }
+ }
+
+ /**
+ * See {@link android.webkit.WebSettings#getDefaultFontSize}.
+ */
+ public int getDefaultFontSize() {
+ synchronized (mAwSettingsLock) {
+ return mDefaultFontSize;
+ }
+ }
+
+ @CalledByNative
+ private int getDefaultFontSizeLocked() {
+ return mDefaultFontSize;
+ }
+
+ /**
+ * See {@link android.webkit.WebSettings#setDefaultFixedFontSize}.
+ */
+ public void setDefaultFixedFontSize(int size) {
+ synchronized (mAwSettingsLock) {
+ size = clipFontSize(size);
+ if (mDefaultFixedFontSize != size) {
+ mDefaultFixedFontSize = size;
+ mEventHandler.updateWebkitPreferencesLocked();
+ }
+ }
+ }
+
+ /**
+ * See {@link android.webkit.WebSettings#getDefaultFixedFontSize}.
+ */
+ public int getDefaultFixedFontSize() {
+ synchronized (mAwSettingsLock) {
+ return mDefaultFixedFontSize;
+ }
+ }
+
+ @CalledByNative
+ private int getDefaultFixedFontSizeLocked() {
+ return mDefaultFixedFontSize;
+ }
+
+ /**
+ * See {@link android.webkit.WebSettings#setJavaScriptEnabled}.
+ */
+ public void setJavaScriptEnabled(boolean flag) {
+ synchronized (mAwSettingsLock) {
+ if (mJavaScriptEnabled != flag) {
+ mJavaScriptEnabled = flag;
+ mEventHandler.updateWebkitPreferencesLocked();
+ }
+ }
+ }
+
+ /**
+ * See {@link android.webkit.WebSettings#setAllowUniversalAccessFromFileURLs}.
+ */
+ public void setAllowUniversalAccessFromFileURLs(boolean flag) {
+ synchronized (mAwSettingsLock) {
+ if (mAllowUniversalAccessFromFileURLs != flag) {
+ mAllowUniversalAccessFromFileURLs = flag;
+ mEventHandler.updateWebkitPreferencesLocked();
+ }
+ }
+ }
+
+ /**
+ * See {@link android.webkit.WebSettings#setAllowFileAccessFromFileURLs}.
+ */
+ public void setAllowFileAccessFromFileURLs(boolean flag) {
+ synchronized (mAwSettingsLock) {
+ if (mAllowFileAccessFromFileURLs != flag) {
+ mAllowFileAccessFromFileURLs = flag;
+ mEventHandler.updateWebkitPreferencesLocked();
+ }
+ }
+ }
+
+ /**
+ * See {@link android.webkit.WebSettings#setLoadsImagesAutomatically}.
+ */
+ public void setLoadsImagesAutomatically(boolean flag) {
+ synchronized (mAwSettingsLock) {
+ if (mLoadsImagesAutomatically != flag) {
+ mLoadsImagesAutomatically = flag;
+ mEventHandler.updateWebkitPreferencesLocked();
+ }
+ }
+ }
+
+ /**
+ * See {@link android.webkit.WebSettings#getLoadsImagesAutomatically}.
+ */
+ public boolean getLoadsImagesAutomatically() {
+ synchronized (mAwSettingsLock) {
+ return mLoadsImagesAutomatically;
+ }
+ }
+
+ @CalledByNative
+ private boolean getLoadsImagesAutomaticallyLocked() {
+ return mLoadsImagesAutomatically;
+ }
+
+ /**
+ * See {@link android.webkit.WebSettings#setImagesEnabled}.
+ */
+ public void setImagesEnabled(boolean flag) {
+ synchronized (mAwSettingsLock) {
+ if (mImagesEnabled != flag) {
+ mImagesEnabled = flag;
+ mEventHandler.updateWebkitPreferencesLocked();
+ }
+ }
+ }
+
+ /**
+ * See {@link android.webkit.WebSettings#getImagesEnabled}.
+ */
+ public boolean getImagesEnabled() {
+ synchronized (mAwSettingsLock) {
+ return mImagesEnabled;
+ }
+ }
+
+ @CalledByNative
+ private boolean getImagesEnabledLocked() {
+ return mImagesEnabled;
+ }
+
+ /**
+ * See {@link android.webkit.WebSettings#getJavaScriptEnabled}.
+ */
+ public boolean getJavaScriptEnabled() {
+ synchronized (mAwSettingsLock) {
+ return mJavaScriptEnabled;
+ }
+ }
+
+ @CalledByNative
+ private boolean getJavaScriptEnabledLocked() {
+ return mJavaScriptEnabled;
+ }
+
+ /**
+ * See {@link android.webkit.WebSettings#getAllowUniversalAccessFromFileURLs}.
+ */
+ public boolean getAllowUniversalAccessFromFileURLs() {
+ synchronized (mAwSettingsLock) {
+ return mAllowUniversalAccessFromFileURLs;
+ }
+ }
+
+ @CalledByNative
+ private boolean getAllowUniversalAccessFromFileURLsLocked() {
+ return mAllowUniversalAccessFromFileURLs;
+ }
+
+ /**
+ * See {@link android.webkit.WebSettings#getAllowFileAccessFromFileURLs}.
+ */
+ public boolean getAllowFileAccessFromFileURLs() {
+ synchronized (mAwSettingsLock) {
+ return mAllowFileAccessFromFileURLs;
+ }
+ }
+
+ @CalledByNative
+ private boolean getAllowFileAccessFromFileURLsLocked() {
+ return mAllowFileAccessFromFileURLs;
+ }
+
+ /**
+ * See {@link android.webkit.WebSettings#setPluginsEnabled}.
+ */
+ @Deprecated
+ public void setPluginsEnabled(boolean flag) {
+ setPluginState(flag ? PluginState.ON : PluginState.OFF);
+ }
+
+ /**
+ * See {@link android.webkit.WebSettings#setPluginState}.
+ */
+ public void setPluginState(PluginState state) {
+ synchronized (mAwSettingsLock) {
+ if (mPluginState != state) {
+ mPluginState = state;
+ mEventHandler.updateWebkitPreferencesLocked();
+ }
+ }
+ }
+
+ /**
+ * See {@link android.webkit.WebSettings#getPluginsEnabled}.
+ */
+ @Deprecated
+ public boolean getPluginsEnabled() {
+ synchronized (mAwSettingsLock) {
+ return mPluginState == PluginState.ON;
+ }
+ }
+
+ /**
+ * Return true if plugins are disabled.
+ * @return True if plugins are disabled.
+ * @hide
+ */
+ @CalledByNative
+ private boolean getPluginsDisabledLocked() {
+ return mPluginState == PluginState.OFF;
+ }
+
+ /**
+ * See {@link android.webkit.WebSettings#getPluginState}.
+ */
+ public PluginState getPluginState() {
+ synchronized (mAwSettingsLock) {
+ return mPluginState;
+ }
+ }
+
+
+ /**
+ * See {@link android.webkit.WebSettings#setJavaScriptCanOpenWindowsAutomatically}.
+ */
+ public void setJavaScriptCanOpenWindowsAutomatically(boolean flag) {
+ synchronized (mAwSettingsLock) {
+ if (mJavaScriptCanOpenWindowsAutomatically != flag) {
+ mJavaScriptCanOpenWindowsAutomatically = flag;
+ mEventHandler.updateWebkitPreferencesLocked();
+ }
+ }
+ }
+
+ /**
+ * See {@link android.webkit.WebSettings#getJavaScriptCanOpenWindowsAutomatically}.
+ */
+ public boolean getJavaScriptCanOpenWindowsAutomatically() {
+ synchronized (mAwSettingsLock) {
+ return mJavaScriptCanOpenWindowsAutomatically;
+ }
+ }
+
+ @CalledByNative
+ private boolean getJavaScriptCanOpenWindowsAutomaticallyLocked() {
+ return mJavaScriptCanOpenWindowsAutomatically;
+ }
+
+ /**
+ * See {@link android.webkit.WebSettings#setLayoutAlgorithm}.
+ */
+ public void setLayoutAlgorithm(LayoutAlgorithm l) {
+ synchronized (mAwSettingsLock) {
+ if (mLayoutAlgorithm != l) {
+ mLayoutAlgorithm = l;
+ mEventHandler.updateWebkitPreferencesLocked();
+ }
+ }
+ }
+
+ /**
+ * See {@link android.webkit.WebSettings#getLayoutAlgorithm}.
+ */
+ public LayoutAlgorithm getLayoutAlgorithm() {
+ synchronized (mAwSettingsLock) {
+ return mLayoutAlgorithm;
+ }
+ }
+
+ /**
+ * Gets whether Text Auto-sizing layout algorithm is enabled.
+ *
+ * @return true if Text Auto-sizing layout algorithm is enabled
+ * @hide
+ */
+ @CalledByNative
+ private boolean getTextAutosizingEnabledLocked() {
+ return mLayoutAlgorithm == LayoutAlgorithm.TEXT_AUTOSIZING;
+ }
+
+ /**
+ * See {@link android.webkit.WebSettings#setSupportMultipleWindows}.
+ */
+ public void setSupportMultipleWindows(boolean support) {
+ synchronized (mAwSettingsLock) {
+ if (mSupportMultipleWindows != support) {
+ mSupportMultipleWindows = support;
+ mEventHandler.updateWebkitPreferencesLocked();
+ }
+ }
+ }
+
+ /**
+ * See {@link android.webkit.WebSettings#supportMultipleWindows}.
+ */
+ public boolean supportMultipleWindows() {
+ synchronized (mAwSettingsLock) {
+ return mSupportMultipleWindows;
+ }
+ }
+
+ @CalledByNative
+ private boolean getSupportMultipleWindowsLocked() {
+ return mSupportMultipleWindows;
+ }
+
+ @CalledByNative
+ private boolean getSupportDeprecatedTargetDensityDPILocked() {
+ return mSupportDeprecatedTargetDensityDPI;
+ }
+
+ /**
+ * See {@link android.webkit.WebSettings#setUseWideViewPort}.
+ */
+ public void setUseWideViewPort(boolean use) {
+ synchronized (mAwSettingsLock) {
+ if (mUseWideViewport != use) {
+ mUseWideViewport = use;
+ mEventHandler.updateWebkitPreferencesLocked();
+ }
+ }
+ }
+
+ /**
+ * See {@link android.webkit.WebSettings#getUseWideViewPort}.
+ */
+ public boolean getUseWideViewPort() {
+ synchronized (mAwSettingsLock) {
+ return mUseWideViewport;
+ }
+ }
+
+ @CalledByNative
+ private boolean getUseWideViewportLocked() {
+ return mUseWideViewport;
+ }
+
+ /**
+ * See {@link android.webkit.WebSettings#setAppCacheEnabled}.
+ */
+ public void setAppCacheEnabled(boolean flag) {
+ synchronized (mAwSettingsLock) {
+ if (mAppCacheEnabled != flag) {
+ mAppCacheEnabled = flag;
+ mEventHandler.updateWebkitPreferencesLocked();
+ }
+ }
+ }
+
+ /**
+ * See {@link android.webkit.WebSettings#setAppCachePath}.
+ */
+ public void setAppCachePath(String path) {
+ boolean needToSync = false;
+ synchronized (sGlobalContentSettingsLock) {
+ // AppCachePath can only be set once.
+ if (!sAppCachePathIsSet && path != null && !path.isEmpty()) {
+ sAppCachePathIsSet = true;
+ needToSync = true;
+ }
+ }
+ // The obvious problem here is that other WebViews will not be updated,
+ // until they execute synchronization from Java to the native side.
+ // But this is the same behaviour as it was in the legacy WebView.
+ if (needToSync) {
+ synchronized (mAwSettingsLock) {
+ mEventHandler.updateWebkitPreferencesLocked();
+ }
+ }
+ }
+
+ /**
+ * Gets whether Application Cache is enabled.
+ *
+ * @return true if Application Cache is enabled
+ * @hide
+ */
+ @CalledByNative
+ private boolean getAppCacheEnabledLocked() {
+ if (!mAppCacheEnabled) {
+ return false;
+ }
+ synchronized (sGlobalContentSettingsLock) {
+ return sAppCachePathIsSet;
+ }
+ }
+
+ /**
+ * See {@link android.webkit.WebSettings#setDomStorageEnabled}.
+ */
+ public void setDomStorageEnabled(boolean flag) {
+ synchronized (mAwSettingsLock) {
+ if (mDomStorageEnabled != flag) {
+ mDomStorageEnabled = flag;
+ mEventHandler.updateWebkitPreferencesLocked();
+ }
+ }
+ }
+
+ /**
+ * See {@link android.webkit.WebSettings#getDomStorageEnabled}.
+ */
+ public boolean getDomStorageEnabled() {
+ synchronized (mAwSettingsLock) {
+ return mDomStorageEnabled;
+ }
+ }
+
+ @CalledByNative
+ private boolean getDomStorageEnabledLocked() {
+ return mDomStorageEnabled;
+ }
+
+ /**
+ * See {@link android.webkit.WebSettings#setDatabaseEnabled}.
+ */
+ public void setDatabaseEnabled(boolean flag) {
+ synchronized (mAwSettingsLock) {
+ if (mDatabaseEnabled != flag) {
+ mDatabaseEnabled = flag;
+ mEventHandler.updateWebkitPreferencesLocked();
+ }
+ }
+ }
+
+ /**
+ * See {@link android.webkit.WebSettings#getDatabaseEnabled}.
+ */
+ public boolean getDatabaseEnabled() {
+ synchronized (mAwSettingsLock) {
+ return mDatabaseEnabled;
+ }
+ }
+
+ @CalledByNative
+ private boolean getDatabaseEnabledLocked() {
+ return mDatabaseEnabled;
+ }
+
+ /**
+ * See {@link android.webkit.WebSettings#setDefaultTextEncodingName}.
+ */
+ public void setDefaultTextEncodingName(String encoding) {
+ synchronized (mAwSettingsLock) {
+ if (encoding != null && !mDefaultTextEncoding.equals(encoding)) {
+ mDefaultTextEncoding = encoding;
+ mEventHandler.updateWebkitPreferencesLocked();
+ }
+ }
+ }
+
+ /**
+ * See {@link android.webkit.WebSettings#getDefaultTextEncodingName}.
+ */
+ public String getDefaultTextEncodingName() {
+ synchronized (mAwSettingsLock) {
+ return mDefaultTextEncoding;
+ }
+ }
+
+ @CalledByNative
+ private String getDefaultTextEncodingLocked() {
+ return mDefaultTextEncoding;
+ }
+
+ /**
+ * See {@link android.webkit.WebSettings#setMediaPlaybackRequiresUserGesture}.
+ */
+ public void setMediaPlaybackRequiresUserGesture(boolean require) {
+ synchronized (mAwSettingsLock) {
+ if (mMediaPlaybackRequiresUserGesture != require) {
+ mMediaPlaybackRequiresUserGesture = require;
+ mEventHandler.updateWebkitPreferencesLocked();
+ }
+ }
+ }
+
+ /**
+ * See {@link android.webkit.WebSettings#getMediaPlaybackRequiresUserGesture}.
+ */
+ public boolean getMediaPlaybackRequiresUserGesture() {
+ synchronized (mAwSettingsLock) {
+ return mMediaPlaybackRequiresUserGesture;
+ }
+ }
+
+ @CalledByNative
+ private boolean getMediaPlaybackRequiresUserGestureLocked() {
+ return mMediaPlaybackRequiresUserGesture;
+ }
+
+ /**
+ * See {@link android.webkit.WebSettings#setDefaultVideoPosterURL}.
+ */
+ public void setDefaultVideoPosterURL(String url) {
+ synchronized (mAwSettingsLock) {
+ if (mDefaultVideoPosterURL != null && !mDefaultVideoPosterURL.equals(url) ||
+ mDefaultVideoPosterURL == null && url != null) {
+ mDefaultVideoPosterURL = url;
+ mEventHandler.updateWebkitPreferencesLocked();
+ }
+ }
+ }
+
+ /**
+ * See {@link android.webkit.WebSettings#getDefaultVideoPosterURL}.
+ */
+ public String getDefaultVideoPosterURL() {
+ synchronized (mAwSettingsLock) {
+ return mDefaultVideoPosterURL;
+ }
+ }
+
+ @CalledByNative
+ private String getDefaultVideoPosterURLLocked() {
+ return mDefaultVideoPosterURL;
+ }
+
+ private void updateMultiTouchZoomSupport(final boolean supportsMultiTouchZoom) {
+ ThreadUtils.runOnUiThreadBlocking(new Runnable() {
+ @Override
+ public void run() {
+ mContentViewCore.updateMultiTouchZoomSupport(supportsMultiTouchZoom);
+ }
+ });
+ }
+
+ /**
+ * See {@link android.webkit.WebSettings#setSupportZoom}.
+ */
+ public void setSupportZoom(boolean support) {
+ synchronized (mAwSettingsLock) {
+ if (mSupportZoom != support) {
+ mSupportZoom = support;
+ updateMultiTouchZoomSupport(supportsMultiTouchZoomLocked());
+ }
+ }
+ }
+
+ /**
+ * See {@link android.webkit.WebSettings#supportZoom}.
+ */
+ public boolean supportZoom() {
+ synchronized (mAwSettingsLock) {
+ return mSupportZoom;
+ }
+ }
+
+ /**
+ * See {@link android.webkit.WebSettings#setBuiltInZoomControls}.
+ */
+ public void setBuiltInZoomControls(boolean enabled) {
+ synchronized (mAwSettingsLock) {
+ if (mBuiltInZoomControls != enabled) {
+ mBuiltInZoomControls = enabled;
+ updateMultiTouchZoomSupport(supportsMultiTouchZoomLocked());
+ }
+ }
+ }
+
+ /**
+ * See {@link android.webkit.WebSettings#getBuiltInZoomControls}.
+ */
+ public boolean getBuiltInZoomControls() {
+ synchronized (mAwSettingsLock) {
+ return mBuiltInZoomControls;
+ }
+ }
+
+ /**
+ * See {@link android.webkit.WebSettings#setDisplayZoomControls}.
+ */
+ public void setDisplayZoomControls(boolean enabled) {
+ synchronized (mAwSettingsLock) {
+ mDisplayZoomControls = enabled;
+ }
+ }
+
+ /**
+ * See {@link android.webkit.WebSettings#getDisplayZoomControls}.
+ */
+ public boolean getDisplayZoomControls() {
+ synchronized (mAwSettingsLock) {
+ return mDisplayZoomControls;
+ }
+ }
+
+ private boolean supportsMultiTouchZoomLocked() {
+ return mSupportZoom && mBuiltInZoomControls;
+ }
+
+ boolean supportsMultiTouchZoom() {
+ synchronized (mAwSettingsLock) {
+ return supportsMultiTouchZoomLocked();
+ }
+ }
+
+ boolean shouldDisplayZoomControls() {
+ synchronized (mAwSettingsLock) {
+ return supportsMultiTouchZoomLocked() && mDisplayZoomControls;
+ }
+ }
+
+ private int clipFontSize(int size) {
+ if (size < MINIMUM_FONT_SIZE) {
+ return MINIMUM_FONT_SIZE;
+ } else if (size > MAXIMUM_FONT_SIZE) {
+ return MAXIMUM_FONT_SIZE;
+ }
+ return size;
+ }
+
+ @CalledByNative
+ private void updateEverything() {
+ synchronized (mAwSettingsLock) {
+ nativeUpdateEverythingLocked(mNativeAwSettings);
+ }
+ }
+
+ private void updateWebkitPreferencesOnUiThreadLocked() {
+ if (mNativeAwSettings != 0) {
+ ThreadUtils.assertOnUiThread();
+ nativeUpdateWebkitPreferencesLocked(mNativeAwSettings);
+ }
+ }
+
+ private native int nativeInit(int webContentsPtr);
+
+ private native void nativeDestroy(int nativeAwSettings);
+
+ private native void nativeResetScrollAndScaleState(int nativeAwSettings);
+
+ private native void nativeSetWebContentsLocked(int nativeAwSettings, int nativeWebContents);
+
+ private native void nativeUpdateEverythingLocked(int nativeAwSettings);
+
+ private native void nativeUpdateInitialPageScaleLocked(int nativeAwSettings);
+
+ private native void nativeUpdateUserAgentLocked(int nativeAwSettings);
+
+ private native void nativeUpdateWebkitPreferencesLocked(int nativeAwSettings);
+
+ private static native String nativeGetDefaultUserAgent();
+
+ private native void nativeUpdateFormDataPreferencesLocked(int nativeAwSettings);
+}
diff --git a/src/org/chromium/android_webview/AwWebContentsDelegate.java b/src/org/chromium/android_webview/AwWebContentsDelegate.java
new file mode 100644
index 0000000..23f48eb
--- /dev/null
+++ b/src/org/chromium/android_webview/AwWebContentsDelegate.java
@@ -0,0 +1,36 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+package org.chromium.android_webview;
+
+import org.chromium.base.CalledByNative;
+import org.chromium.base.JNINamespace;
+import org.chromium.components.web_contents_delegate_android.WebContentsDelegateAndroid;
+
+/**
+ * WebView-specific WebContentsDelegate.
+ * This file is the Java version of the native class of the same name.
+ * It should contain abstract WebContentsDelegate methods to be implemented by the embedder.
+ * These methods belong to WebView but are not shared with the Chromium Android port.
+ */
+@JNINamespace("android_webview")
+public abstract class AwWebContentsDelegate extends WebContentsDelegateAndroid {
+ @CalledByNative
+ public abstract boolean addNewContents(boolean isDialog, boolean isUserGesture);
+
+ @CalledByNative
+ public abstract void closeContents();
+
+ @CalledByNative
+ public abstract void activateContents();
+
+ /**
+ * Report a change in the preferred size.
+ * @param width preferred width in CSS pixels.
+ * @param height scroll height of the document element in CSS pixels.
+ */
+ @CalledByNative
+ public void updatePreferredSize(int widthCss, int heightCss) {
+ }
+}
diff --git a/src/org/chromium/android_webview/AwWebContentsDelegateAdapter.java b/src/org/chromium/android_webview/AwWebContentsDelegateAdapter.java
new file mode 100644
index 0000000..0b888d3
--- /dev/null
+++ b/src/org/chromium/android_webview/AwWebContentsDelegateAdapter.java
@@ -0,0 +1,152 @@
+// Copyright 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+package org.chromium.android_webview;
+
+import android.graphics.Rect;
+import android.os.Handler;
+import android.os.Looper;
+import android.os.Message;
+import android.util.Log;
+import android.view.KeyEvent;
+import android.webkit.ConsoleMessage;
+
+import org.chromium.content.browser.ContentViewCore;
+
+/**
+ * Adapts the AwWebContentsDelegate interface to the AwContentsClient interface.
+ * This class also serves a secondary function of routing certain callbacks from the content layer
+ * to specific listener interfaces.
+ */
+class AwWebContentsDelegateAdapter extends AwWebContentsDelegate {
+ private static final String TAG = "AwWebContentsDelegateAdapter";
+
+ /**
+ * Listener definition for a callback to be invoked when the preferred size of the page
+ * contents changes.
+ */
+ public interface PreferredSizeChangedListener {
+ /**
+ * Called when the preferred size of the page contents changes.
+ * @see AwWebContentsDelegate#updatePreferredSize
+ */
+ void updatePreferredSize(int width, int height);
+ }
+
+ final AwContentsClient mContentsClient;
+ final PreferredSizeChangedListener mPreferredSizeChangedListener;
+
+ public AwWebContentsDelegateAdapter(AwContentsClient contentsClient,
+ PreferredSizeChangedListener preferredSizeChangedListener) {
+ mContentsClient = contentsClient;
+ mPreferredSizeChangedListener = preferredSizeChangedListener;
+ }
+
+ @Override
+ public void onLoadProgressChanged(int progress) {
+ mContentsClient.onProgressChanged(progress);
+ }
+
+ @Override
+ public void handleKeyboardEvent(KeyEvent event) {
+ mContentsClient.onUnhandledKeyEvent(event);
+ }
+
+ @Override
+ public boolean addMessageToConsole(int level, String message, int lineNumber,
+ String sourceId) {
+ ConsoleMessage.MessageLevel messageLevel = ConsoleMessage.MessageLevel.DEBUG;
+ switch(level) {
+ case LOG_LEVEL_TIP:
+ messageLevel = ConsoleMessage.MessageLevel.TIP;
+ break;
+ case LOG_LEVEL_LOG:
+ messageLevel = ConsoleMessage.MessageLevel.LOG;
+ break;
+ case LOG_LEVEL_WARNING:
+ messageLevel = ConsoleMessage.MessageLevel.WARNING;
+ break;
+ case LOG_LEVEL_ERROR:
+ messageLevel = ConsoleMessage.MessageLevel.ERROR;
+ break;
+ default:
+ Log.w(TAG, "Unknown message level, defaulting to DEBUG");
+ break;
+ }
+
+ return mContentsClient.onConsoleMessage(
+ new ConsoleMessage(message, sourceId, lineNumber, messageLevel));
+ }
+
+ @Override
+ public void onUpdateUrl(String url) {
+ // TODO: implement
+ }
+
+ @Override
+ public void openNewTab(String url, boolean incognito) {
+ // TODO: implement
+ }
+
+ @Override
+ public boolean addNewContents(int nativeSourceWebContents, int nativeWebContents,
+ int disposition, Rect initialPosition, boolean userGesture) {
+ // TODO: implement
+ return false;
+ }
+
+ @Override
+ public void closeContents() {
+ mContentsClient.onCloseWindow();
+ }
+
+ @Override
+ public void showRepostFormWarningDialog(final ContentViewCore contentViewCore) {
+ // TODO(mkosiba) We should be using something akin to the JsResultReceiver as the
+ // callback parameter (instead of ContentViewCore) and implement a way of converting
+ // that to a pair of messages.
+ final int MSG_CONTINUE_PENDING_RELOAD = 1;
+ final int MSG_CANCEL_PENDING_RELOAD = 2;
+
+ // TODO(sgurun) Remember the URL to cancel the reload behavior
+ // if it is different than the most recent NavigationController entry.
+ final Handler handler = new Handler(Looper.getMainLooper()) {
+ @Override
+ public void handleMessage(Message msg) {
+ switch(msg.what) {
+ case MSG_CONTINUE_PENDING_RELOAD: {
+ contentViewCore.continuePendingReload();
+ break;
+ }
+ case MSG_CANCEL_PENDING_RELOAD: {
+ contentViewCore.cancelPendingReload();
+ break;
+ }
+ default:
+ throw new IllegalStateException(
+ "WebContentsDelegateAdapter: unhandled message " + msg.what);
+ }
+ }
+ };
+
+ Message resend = handler.obtainMessage(MSG_CONTINUE_PENDING_RELOAD);
+ Message dontResend = handler.obtainMessage(MSG_CANCEL_PENDING_RELOAD);
+ mContentsClient.onFormResubmission(dontResend, resend);
+ }
+
+ @Override
+ public boolean addNewContents(boolean isDialog, boolean isUserGesture) {
+ return mContentsClient.onCreateWindow(isDialog, isUserGesture);
+ }
+
+ @Override
+ public void activateContents() {
+ mContentsClient.onRequestFocus();
+ }
+
+ @Override
+ public void updatePreferredSize(int width, int height) {
+ mPreferredSizeChangedListener.updatePreferredSize(width, height);
+ }
+}
diff --git a/src/org/chromium/android_webview/AwZoomControls.java b/src/org/chromium/android_webview/AwZoomControls.java
new file mode 100644
index 0000000..0bcae18
--- /dev/null
+++ b/src/org/chromium/android_webview/AwZoomControls.java
@@ -0,0 +1,102 @@
+// Copyright (c) 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+package org.chromium.android_webview;
+
+import android.view.Gravity;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.FrameLayout;
+import android.widget.ZoomButtonsController;
+import org.chromium.content.browser.ContentViewCore.ZoomControlsDelegate;
+
+class AwZoomControls implements ZoomControlsDelegate {
+
+ private AwContents mAwContents;
+ // It is advised to use getZoomController() where possible.
+ private ZoomButtonsController mZoomButtonsController;
+
+ AwZoomControls(AwContents awContents) {
+ mAwContents = awContents;
+ }
+
+ @Override
+ public void invokeZoomPicker() {
+ ZoomButtonsController zoomController = getZoomController();
+ if (zoomController != null) {
+ zoomController.setVisible(true);
+ }
+ }
+
+ @Override
+ public void dismissZoomPicker() {
+ ZoomButtonsController zoomController = getZoomController();
+ if (zoomController != null) {
+ zoomController.setVisible(false);
+ }
+ }
+
+ @Override
+ public void updateZoomControls() {
+ ZoomButtonsController zoomController = getZoomController();
+ if (zoomController == null) {
+ return;
+ }
+ boolean canZoomIn = mAwContents.canZoomIn();
+ boolean canZoomOut = mAwContents.canZoomOut();
+ if (!canZoomIn && !canZoomOut) {
+ // Hide the zoom in and out buttons if the page cannot zoom
+ zoomController.getZoomControls().setVisibility(View.GONE);
+ } else {
+ // Set each one individually, as a page may be able to zoom in or out
+ zoomController.setZoomInEnabled(canZoomIn);
+ zoomController.setZoomOutEnabled(canZoomOut);
+ }
+ }
+
+ // This method is used in tests. It doesn't modify the state of zoom controls.
+ View getZoomControlsViewForTest() {
+ return mZoomButtonsController != null ? mZoomButtonsController.getZoomControls() : null;
+ }
+
+ private ZoomButtonsController getZoomController() {
+ if (mZoomButtonsController == null &&
+ mAwContents.getSettings().shouldDisplayZoomControls()) {
+ mZoomButtonsController = new ZoomButtonsController(
+ mAwContents.getContentViewCore().getContainerView());
+ mZoomButtonsController.setOnZoomListener(new ZoomListener());
+ // ZoomButtonsController positions the buttons at the bottom, but in
+ // the middle. Change their layout parameters so they appear on the
+ // right.
+ View controls = mZoomButtonsController.getZoomControls();
+ ViewGroup.LayoutParams params = controls.getLayoutParams();
+ if (params instanceof FrameLayout.LayoutParams) {
+ ((FrameLayout.LayoutParams) params).gravity = Gravity.RIGHT;
+ }
+ }
+ return mZoomButtonsController;
+ }
+
+ private class ZoomListener implements ZoomButtonsController.OnZoomListener {
+ @Override
+ public void onVisibilityChanged(boolean visible) {
+ if (visible) {
+ // Bring back the hidden zoom controls.
+ mZoomButtonsController.getZoomControls().setVisibility(View.VISIBLE);
+ updateZoomControls();
+ }
+ }
+
+ @Override
+ public void onZoom(boolean zoomIn) {
+ if (zoomIn) {
+ mAwContents.zoomIn();
+ } else {
+ mAwContents.zoomOut();
+ }
+ // ContentView will call updateZoomControls after its current page scale
+ // is got updated from the native code.
+ }
+ }
+}
diff --git a/src/org/chromium/android_webview/DefaultVideoPosterRequestHandler.java b/src/org/chromium/android_webview/DefaultVideoPosterRequestHandler.java
new file mode 100644
index 0000000..22d57ca
--- /dev/null
+++ b/src/org/chromium/android_webview/DefaultVideoPosterRequestHandler.java
@@ -0,0 +1,110 @@
+// Copyright 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+package org.chromium.android_webview;
+
+import android.graphics.Bitmap;
+import android.os.AsyncTask;
+import android.util.Log;
+
+import org.chromium.base.ThreadUtils;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.io.PipedInputStream;
+import java.io.PipedOutputStream;
+import java.util.Random;
+
+/**
+ * This class takes advantage of shouldInterceptRequest(), returns the bitmap from
+ * WebChromeClient.getDefaultVidoePoster() when the mDefaultVideoPosterURL is requested.
+ *
+ * The shouldInterceptRequest is used to get the default video poster, if the url is
+ * the mDefaultVideoPosterURL.
+ */
+public class DefaultVideoPosterRequestHandler {
+ private static InputStream getInputStream(final AwContentsClient contentClient)
+ throws IOException {
+ final PipedInputStream inputStream = new PipedInputStream();
+ final PipedOutputStream outputStream = new PipedOutputStream(inputStream);
+
+ // Send the request to UI thread to callback to the client, and if it provides a
+ // valid bitmap bounce on to the worker thread pool to compress it into the piped
+ // input/output stream.
+ ThreadUtils.runOnUiThread(new Runnable() {
+ @Override
+ public void run() {
+ final Bitmap defaultVideoPoster = contentClient.getDefaultVideoPoster();
+ if (defaultVideoPoster == null) {
+ closeOutputStream(outputStream);
+ return;
+ }
+ AsyncTask.THREAD_POOL_EXECUTOR.execute(new Runnable() {
+ @Override
+ public void run() {
+ try {
+ defaultVideoPoster.compress(Bitmap.CompressFormat.PNG, 100,
+ outputStream);
+ outputStream.flush();
+ } catch (IOException e) {
+ Log.e(TAG, null, e);
+ } finally {
+ closeOutputStream(outputStream);
+ }
+ }
+ });
+ }
+ });
+ return inputStream;
+ }
+
+ private static void closeOutputStream(OutputStream outputStream) {
+ try {
+ outputStream.close();
+ } catch (IOException e) {
+ Log.e(TAG, null, e);
+ }
+ }
+
+ private static final String TAG = "DefaultVideoPosterRequestHandler";
+ private String mDefaultVideoPosterURL;
+ private AwContentsClient mContentClient;
+
+ public DefaultVideoPosterRequestHandler(AwContentsClient contentClient) {
+ mDefaultVideoPosterURL = GenerateDefaulVideoPosterURL();
+ mContentClient = contentClient;
+ }
+
+ /**
+ * Used to get the image if the url is mDefaultVideoPosterURL.
+ *
+ * @param url the url requested
+ * @return InterceptedRequestData which caller can get the image if the url is
+ * the default video poster URL, otherwise null is returned.
+ */
+ public InterceptedRequestData shouldInterceptRequest(final String url) {
+ if (!mDefaultVideoPosterURL.equals(url)) return null;
+
+ try {
+ return new InterceptedRequestData("image/png", null, getInputStream(mContentClient));
+ } catch (IOException e) {
+ Log.e(TAG, null, e);
+ return null;
+ }
+ }
+
+ public String getDefaultVideoPosterURL() {
+ return mDefaultVideoPosterURL;
+ }
+
+ /**
+ * @return a unique URL which has little chance to be used by application.
+ */
+ private static String GenerateDefaulVideoPosterURL() {
+ Random randomGenerator = new Random();
+ String path = String.valueOf(randomGenerator.nextLong());
+ return "android-webview:default_video_poster/" + path;
+ }
+}
diff --git a/src/org/chromium/android_webview/ErrorCodeConversionHelper.java b/src/org/chromium/android_webview/ErrorCodeConversionHelper.java
new file mode 100644
index 0000000..9b4d377
--- /dev/null
+++ b/src/org/chromium/android_webview/ErrorCodeConversionHelper.java
@@ -0,0 +1,143 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+package org.chromium.android_webview;
+
+import org.chromium.net.NetError;
+
+/**
+ * This is a helper class to map native error code about loading a page to Android specific ones.
+ */
+public abstract class ErrorCodeConversionHelper {
+ // Success
+ public static final int ERROR_OK = 0;
+ // Generic error
+ public static final int ERROR_UNKNOWN = -1;
+ // Server or proxy hostname lookup failed
+ public static final int ERROR_HOST_LOOKUP = -2;
+ // Unsupported authentication scheme (not basic or digest)
+ public static final int ERROR_UNSUPPORTED_AUTH_SCHEME = -3;
+ // User authentication failed on server
+ public static final int ERROR_AUTHENTICATION = -4;
+ // User authentication failed on proxy
+ public static final int ERROR_PROXY_AUTHENTICATION = -5;
+ // Failed to connect to the server
+ public static final int ERROR_CONNECT = -6;
+ // Failed to read or write to the server
+ public static final int ERROR_IO = -7;
+ // Connection timed out
+ public static final int ERROR_TIMEOUT = -8;
+ // Too many redirects
+ public static final int ERROR_REDIRECT_LOOP = -9;
+ // Unsupported URI scheme
+ public static final int ERROR_UNSUPPORTED_SCHEME = -10;
+ // Failed to perform SSL handshake
+ public static final int ERROR_FAILED_SSL_HANDSHAKE = -11;
+ // Malformed URL
+ public static final int ERROR_BAD_URL = -12;
+ // Generic file error
+ public static final int ERROR_FILE = -13;
+ // File not found
+ public static final int ERROR_FILE_NOT_FOUND = -14;
+ // Too many requests during this load
+ public static final int ERROR_TOO_MANY_REQUESTS = -15;
+
+ static int convertErrorCode(int netError) {
+ // Note: many NetError.Error constants don't have an obvious mapping.
+ // These will be handled by the default case, ERROR_UNKNOWN.
+ switch (netError) {
+ case NetError.ERR_UNSUPPORTED_AUTH_SCHEME:
+ return ERROR_UNSUPPORTED_AUTH_SCHEME;
+
+ case NetError.ERR_INVALID_AUTH_CREDENTIALS:
+ case NetError.ERR_MISSING_AUTH_CREDENTIALS:
+ case NetError.ERR_MISCONFIGURED_AUTH_ENVIRONMENT:
+ return ERROR_AUTHENTICATION;
+
+ case NetError.ERR_TOO_MANY_REDIRECTS:
+ return ERROR_REDIRECT_LOOP;
+
+ case NetError.ERR_UPLOAD_FILE_CHANGED:
+ return ERROR_FILE_NOT_FOUND;
+
+ case NetError.ERR_INVALID_URL:
+ return ERROR_BAD_URL;
+
+ case NetError.ERR_DISALLOWED_URL_SCHEME:
+ case NetError.ERR_UNKNOWN_URL_SCHEME:
+ return ERROR_UNSUPPORTED_SCHEME;
+
+ case NetError.ERR_IO_PENDING:
+ case NetError.ERR_NETWORK_IO_SUSPENDED:
+ return ERROR_IO;
+
+ case NetError.ERR_CONNECTION_TIMED_OUT:
+ case NetError.ERR_TIMED_OUT:
+ return ERROR_TIMEOUT;
+
+ case NetError.ERR_FILE_TOO_BIG:
+ return ERROR_FILE;
+
+ case NetError.ERR_HOST_RESOLVER_QUEUE_TOO_LARGE:
+ case NetError.ERR_INSUFFICIENT_RESOURCES:
+ case NetError.ERR_OUT_OF_MEMORY:
+ return ERROR_TOO_MANY_REQUESTS;
+
+ case NetError.ERR_CONNECTION_CLOSED:
+ case NetError.ERR_CONNECTION_RESET:
+ case NetError.ERR_CONNECTION_REFUSED:
+ case NetError.ERR_CONNECTION_ABORTED:
+ case NetError.ERR_CONNECTION_FAILED:
+ case NetError.ERR_SOCKET_NOT_CONNECTED:
+ return ERROR_CONNECT;
+
+ case NetError.ERR_INTERNET_DISCONNECTED:
+ case NetError.ERR_ADDRESS_INVALID:
+ case NetError.ERR_ADDRESS_UNREACHABLE:
+ case NetError.ERR_NAME_NOT_RESOLVED:
+ case NetError.ERR_NAME_RESOLUTION_FAILED:
+ return ERROR_HOST_LOOKUP;
+
+ case NetError.ERR_SSL_PROTOCOL_ERROR:
+ case NetError.ERR_SSL_CLIENT_AUTH_CERT_NEEDED:
+ case NetError.ERR_TUNNEL_CONNECTION_FAILED:
+ case NetError.ERR_NO_SSL_VERSIONS_ENABLED:
+ case NetError.ERR_SSL_VERSION_OR_CIPHER_MISMATCH:
+ case NetError.ERR_SSL_RENEGOTIATION_REQUESTED:
+ case NetError.ERR_CERT_ERROR_IN_SSL_RENEGOTIATION:
+ case NetError.ERR_BAD_SSL_CLIENT_AUTH_CERT:
+ case NetError.ERR_SSL_NO_RENEGOTIATION:
+ case NetError.ERR_SSL_DECOMPRESSION_FAILURE_ALERT:
+ case NetError.ERR_SSL_BAD_RECORD_MAC_ALERT:
+ case NetError.ERR_SSL_UNSAFE_NEGOTIATION:
+ case NetError.ERR_SSL_WEAK_SERVER_EPHEMERAL_DH_KEY:
+ case NetError.ERR_SSL_CLIENT_AUTH_PRIVATE_KEY_ACCESS_DENIED:
+ case NetError.ERR_SSL_CLIENT_AUTH_CERT_NO_PRIVATE_KEY:
+ return ERROR_FAILED_SSL_HANDSHAKE;
+
+ case NetError.ERR_PROXY_AUTH_UNSUPPORTED:
+ case NetError.ERR_PROXY_AUTH_REQUESTED:
+ case NetError.ERR_PROXY_CONNECTION_FAILED:
+ case NetError.ERR_UNEXPECTED_PROXY_AUTH:
+ return ERROR_PROXY_AUTHENTICATION;
+
+ // The certificate errors are handled by onReceivedSslError
+ // and don't need to be reported here.
+ case NetError.ERR_CERT_COMMON_NAME_INVALID:
+ case NetError.ERR_CERT_DATE_INVALID:
+ case NetError.ERR_CERT_AUTHORITY_INVALID:
+ case NetError.ERR_CERT_CONTAINS_ERRORS:
+ case NetError.ERR_CERT_NO_REVOCATION_MECHANISM:
+ case NetError.ERR_CERT_UNABLE_TO_CHECK_REVOCATION:
+ case NetError.ERR_CERT_REVOKED:
+ case NetError.ERR_CERT_INVALID:
+ case NetError.ERR_CERT_WEAK_SIGNATURE_ALGORITHM:
+ case NetError.ERR_CERT_NON_UNIQUE_NAME:
+ return ERROR_OK;
+
+ default:
+ return ERROR_UNKNOWN;
+ }
+ }
+}
diff --git a/src/org/chromium/android_webview/HttpAuthDatabase.java b/src/org/chromium/android_webview/HttpAuthDatabase.java
new file mode 100644
index 0000000..0286a67
--- /dev/null
+++ b/src/org/chromium/android_webview/HttpAuthDatabase.java
@@ -0,0 +1,264 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+package org.chromium.android_webview;
+
+import android.content.ContentValues;
+import android.content.Context;
+import android.database.Cursor;
+import android.database.sqlite.SQLiteDatabase;
+import android.database.sqlite.SQLiteException;
+import android.util.Log;
+
+/**
+ * This database is used to support WebView's setHttpAuthUsernamePassword and
+ * getHttpAuthUsernamePassword methods, and WebViewDatabase's clearHttpAuthUsernamePassword and
+ * hasHttpAuthUsernamePassword methods.
+ *
+ * While this class is intended to be used as a singleton, this property is not enforced in this
+ * layer, primarily for ease of testing. To line up with the classic implementation and behavior,
+ * there is no specific handling and reporting when SQL errors occur.
+ *
+ * Note on thread-safety: As per the classic implementation, most API functions have thread safety
+ * provided by the underlying SQLiteDatabase instance. The exception is database opening: this
+ * is handled in the dedicated background thread, which also provides a performance gain
+ * if triggered early on (e.g. as a side effect of CookieSyncManager.createInstance() call),
+ * sufficiently in advance of the first blocking usage of the API.
+ */
+public class HttpAuthDatabase {
+
+ private static final String DATABASE_FILE = "http_auth.db";
+
+ private static final String LOGTAG = HttpAuthDatabase.class.getName();
+
+ private static final int DATABASE_VERSION = 1;
+
+ private static HttpAuthDatabase sInstance = null;
+
+ private SQLiteDatabase mDatabase = null;
+
+ private static final String ID_COL = "_id";
+
+ private static final String[] ID_PROJECTION = new String[] {
+ ID_COL
+ };
+
+ // column id strings for "httpauth" table
+ private static final String HTTPAUTH_TABLE_NAME = "httpauth";
+ private static final String HTTPAUTH_HOST_COL = "host";
+ private static final String HTTPAUTH_REALM_COL = "realm";
+ private static final String HTTPAUTH_USERNAME_COL = "username";
+ private static final String HTTPAUTH_PASSWORD_COL = "password";
+
+ /**
+ * Initially false until the background thread completes.
+ */
+ private boolean mInitialized = false;
+
+ /**
+ * Create an instance of HttpAuthDatabase for the named file, and kick-off background
+ * initialization of that database.
+ *
+ * @param context the Context to use for opening the database
+ * @param databaseFile Name of the file to be initialized.
+ */
+ public HttpAuthDatabase(final Context context, final String databaseFile) {
+ new Thread() {
+ @Override
+ public void run() {
+ initOnBackgroundThread(context, databaseFile);
+ }
+ }.start();
+ }
+
+ /**
+ * @deprecated Retained for merge convenience. TODO(joth): remove in next patch.
+ */
+ @Deprecated
+ public static synchronized HttpAuthDatabase getInstance(Context context) {
+ if (sInstance == null) {
+ sInstance = new HttpAuthDatabase(context, DATABASE_FILE);
+ }
+ return sInstance;
+ }
+
+ /**
+ * Initializes the databases and notifies any callers waiting on waitForInit.
+ *
+ * @param context the Context to use for opening the database
+ * @param databaseFile Name of the file to be initialized.
+ */
+ private synchronized void initOnBackgroundThread(Context context, String databaseFile) {
+ if (mInitialized) {
+ return;
+ }
+
+ initDatabase(context, databaseFile);
+
+ // Thread done, notify.
+ mInitialized = true;
+ notifyAll();
+ }
+
+ /**
+ * Opens the database, and upgrades it if necessary.
+ *
+ * @param context the Context to use for opening the database
+ * @param databaseFile Name of the file to be initialized.
+ */
+ private void initDatabase(Context context, String databaseFile) {
+ try {
+ mDatabase = context.openOrCreateDatabase(databaseFile, 0, null);
+ } catch (SQLiteException e) {
+ // try again by deleting the old db and create a new one
+ if (context.deleteDatabase(databaseFile)) {
+ mDatabase = context.openOrCreateDatabase(databaseFile, 0, null);
+ }
+ }
+
+ if (mDatabase == null) {
+ // Not much we can do to recover at this point
+ Log.e(LOGTAG, "Unable to open or create " + databaseFile);
+ return;
+ }
+
+ if (mDatabase.getVersion() != DATABASE_VERSION) {
+ mDatabase.beginTransactionNonExclusive();
+ try {
+ createTable();
+ mDatabase.setTransactionSuccessful();
+ } finally {
+ mDatabase.endTransaction();
+ }
+ }
+ }
+
+ private void createTable() {
+ mDatabase.execSQL("CREATE TABLE " + HTTPAUTH_TABLE_NAME
+ + " (" + ID_COL + " INTEGER PRIMARY KEY, "
+ + HTTPAUTH_HOST_COL + " TEXT, " + HTTPAUTH_REALM_COL
+ + " TEXT, " + HTTPAUTH_USERNAME_COL + " TEXT, "
+ + HTTPAUTH_PASSWORD_COL + " TEXT," + " UNIQUE ("
+ + HTTPAUTH_HOST_COL + ", " + HTTPAUTH_REALM_COL
+ + ") ON CONFLICT REPLACE);");
+
+ mDatabase.setVersion(DATABASE_VERSION);
+ }
+
+ /**
+ * Waits for the background initialization thread to complete and check the database creation
+ * status.
+ *
+ * @return true if the database was initialized, false otherwise
+ */
+ private boolean waitForInit() {
+ synchronized (this) {
+ while (!mInitialized) {
+ try {
+ wait();
+ } catch (InterruptedException e) {
+ Log.e(LOGTAG, "Caught exception while checking initialization", e);
+ }
+ }
+ }
+ return mDatabase != null;
+ }
+
+ /**
+ * Sets the HTTP authentication password. Tuple (HTTPAUTH_HOST_COL, HTTPAUTH_REALM_COL,
+ * HTTPAUTH_USERNAME_COL) is unique.
+ *
+ * @param host the host for the password
+ * @param realm the realm for the password
+ * @param username the username for the password.
+ * @param password the password
+ */
+ public void setHttpAuthUsernamePassword(String host, String realm, String username,
+ String password) {
+ if (host == null || realm == null || !waitForInit()) {
+ return;
+ }
+
+ final ContentValues c = new ContentValues();
+ c.put(HTTPAUTH_HOST_COL, host);
+ c.put(HTTPAUTH_REALM_COL, realm);
+ c.put(HTTPAUTH_USERNAME_COL, username);
+ c.put(HTTPAUTH_PASSWORD_COL, password);
+ mDatabase.insert(HTTPAUTH_TABLE_NAME, HTTPAUTH_HOST_COL, c);
+ }
+
+ /**
+ * Retrieves the HTTP authentication username and password for a given host and realm pair. If
+ * there are multiple username/password combinations for a host/realm, only the first one will
+ * be returned.
+ *
+ * @param host the host the password applies to
+ * @param realm the realm the password applies to
+ * @return a String[] if found where String[0] is username (which can be null) and
+ * String[1] is password. Null is returned if it can't find anything.
+ */
+ public String[] getHttpAuthUsernamePassword(String host, String realm) {
+ if (host == null || realm == null || !waitForInit()){
+ return null;
+ }
+
+ final String[] columns = new String[] {
+ HTTPAUTH_USERNAME_COL, HTTPAUTH_PASSWORD_COL
+ };
+ final String selection = "(" + HTTPAUTH_HOST_COL + " == ?) AND " +
+ "(" + HTTPAUTH_REALM_COL + " == ?)";
+
+ String[] ret = null;
+ Cursor cursor = null;
+ try {
+ cursor = mDatabase.query(HTTPAUTH_TABLE_NAME, columns, selection,
+ new String[] { host, realm }, null, null, null);
+ if (cursor.moveToFirst()) {
+ ret = new String[] {
+ cursor.getString(cursor.getColumnIndex(HTTPAUTH_USERNAME_COL)),
+ cursor.getString(cursor.getColumnIndex(HTTPAUTH_PASSWORD_COL)),
+ };
+ }
+ } catch (IllegalStateException e) {
+ Log.e(LOGTAG, "getHttpAuthUsernamePassword", e);
+ } finally {
+ if (cursor != null) cursor.close();
+ }
+ return ret;
+ }
+
+ /**
+ * Determines if there are any HTTP authentication passwords saved.
+ *
+ * @return true if there are passwords saved
+ */
+ public boolean hasHttpAuthUsernamePassword() {
+ if (!waitForInit()) {
+ return false;
+ }
+
+ Cursor cursor = null;
+ boolean ret = false;
+ try {
+ cursor = mDatabase.query(HTTPAUTH_TABLE_NAME, ID_PROJECTION, null, null, null, null,
+ null);
+ ret = cursor.moveToFirst();
+ } catch (IllegalStateException e) {
+ Log.e(LOGTAG, "hasEntries", e);
+ } finally {
+ if (cursor != null) cursor.close();
+ }
+ return ret;
+ }
+
+ /**
+ * Clears the HTTP authentication password database.
+ */
+ public void clearHttpAuthUsernamePassword() {
+ if (!waitForInit()) {
+ return;
+ }
+ mDatabase.delete(HTTPAUTH_TABLE_NAME, null, null);
+ }
+}
diff --git a/src/org/chromium/android_webview/InterceptedRequestData.java b/src/org/chromium/android_webview/InterceptedRequestData.java
new file mode 100644
index 0000000..f6f3d9d
--- /dev/null
+++ b/src/org/chromium/android_webview/InterceptedRequestData.java
@@ -0,0 +1,41 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+package org.chromium.android_webview;
+
+import org.chromium.base.CalledByNative;
+import org.chromium.base.JNINamespace;
+
+import java.io.InputStream;
+
+/**
+ * The response information that is to be returned for a particular resource fetch.
+ */
+@JNINamespace("android_webview")
+public class InterceptedRequestData {
+ private String mMimeType;
+ private String mCharset;
+ private InputStream mData;
+
+ public InterceptedRequestData(String mimeType, String encoding, InputStream data) {
+ mMimeType = mimeType;
+ mCharset = encoding;
+ mData = data;
+ }
+
+ @CalledByNative
+ public String getMimeType() {
+ return mMimeType;
+ }
+
+ @CalledByNative
+ public String getCharset() {
+ return mCharset;
+ }
+
+ @CalledByNative
+ public InputStream getData() {
+ return mData;
+ }
+}
diff --git a/src/org/chromium/android_webview/JavaBrowserViewRendererHelper.java b/src/org/chromium/android_webview/JavaBrowserViewRendererHelper.java
new file mode 100644
index 0000000..d4e79c8
--- /dev/null
+++ b/src/org/chromium/android_webview/JavaBrowserViewRendererHelper.java
@@ -0,0 +1,56 @@
+// Copyright (c) 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+package org.chromium.android_webview;
+
+import android.graphics.Bitmap;
+import android.graphics.Canvas;
+import android.graphics.Color;
+import android.graphics.Picture;
+
+import org.chromium.base.CalledByNative;
+import org.chromium.base.JNINamespace;
+
+/**
+ * Provides auxiliary methods related to Picture objects and native SkPictures.
+ */
+@JNINamespace("android_webview")
+public class JavaBrowserViewRendererHelper {
+
+ /**
+ * Provides a Bitmap object with a given width and height used for auxiliary rasterization.
+ */
+ @CalledByNative
+ private static Bitmap createBitmap(int width, int height) {
+ return Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
+ }
+
+ /**
+ * Draws a provided bitmap into a canvas.
+ * Used for convenience from the native side and other static helper methods.
+ */
+ @CalledByNative
+ private static void drawBitmapIntoCanvas(Bitmap bitmap, Canvas canvas) {
+ canvas.drawBitmap(bitmap, 0, 0, null);
+ }
+
+ /**
+ * Creates a new Picture that records drawing a provided bitmap.
+ * Will return an empty Picture if the Bitmap is null.
+ */
+ @CalledByNative
+ private static Picture recordBitmapIntoPicture(Bitmap bitmap) {
+ Picture picture = new Picture();
+ if (bitmap != null) {
+ Canvas recordingCanvas = picture.beginRecording(bitmap.getWidth(), bitmap.getHeight());
+ drawBitmapIntoCanvas(bitmap, recordingCanvas);
+ picture.endRecording();
+ }
+ return picture;
+ }
+
+ // Should never be instantiated.
+ private JavaBrowserViewRendererHelper() {
+ }
+}
diff --git a/src/org/chromium/android_webview/JsPromptResultReceiver.java b/src/org/chromium/android_webview/JsPromptResultReceiver.java
new file mode 100644
index 0000000..232140e
--- /dev/null
+++ b/src/org/chromium/android_webview/JsPromptResultReceiver.java
@@ -0,0 +1,17 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+package org.chromium.android_webview;
+
+/**
+ * This interface is used when the AwContentsClient offers a JavaScript
+ * modal prompt dialog to enable the client to handle the dialog in their own way.
+ * AwContentsClient will offer an object that implements this interface to the
+ * client and when the client has handled the dialog, it must either callback with
+ * confirm() or cancel() to allow processing to continue.
+ */
+public interface JsPromptResultReceiver {
+ public void confirm(String result);
+ public void cancel();
+}
diff --git a/src/org/chromium/android_webview/JsResultHandler.java b/src/org/chromium/android_webview/JsResultHandler.java
new file mode 100644
index 0000000..30d4898
--- /dev/null
+++ b/src/org/chromium/android_webview/JsResultHandler.java
@@ -0,0 +1,46 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+package org.chromium.android_webview;
+
+import org.chromium.base.ThreadUtils;
+
+class JsResultHandler implements JsResultReceiver, JsPromptResultReceiver {
+ private AwContentsClientBridge mBridge;
+ private final int mId;
+
+ JsResultHandler(AwContentsClientBridge bridge, int id) {
+ mBridge = bridge;
+ mId = id;
+ }
+
+ @Override
+ public void confirm() {
+ confirm(null);
+ }
+
+ @Override
+ public void confirm(final String promptResult) {
+ ThreadUtils.runOnUiThread(new Runnable() {
+ @Override
+ public void run() {
+ if (mBridge != null)
+ mBridge.confirmJsResult(mId, promptResult);
+ mBridge = null;
+ }
+ });
+ }
+
+ @Override
+ public void cancel() {
+ ThreadUtils.runOnUiThread(new Runnable() {
+ @Override
+ public void run() {
+ if (mBridge != null)
+ mBridge.cancelJsResult(mId);
+ mBridge = null;
+ }
+ });
+ }
+}
diff --git a/src/org/chromium/android_webview/JsResultReceiver.java b/src/org/chromium/android_webview/JsResultReceiver.java
new file mode 100644
index 0000000..30df245
--- /dev/null
+++ b/src/org/chromium/android_webview/JsResultReceiver.java
@@ -0,0 +1,18 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+package org.chromium.android_webview;
+
+/**
+ * This interface is used when the AwContentsClient offers a JavaScript
+ * modal dialog (alert, beforeunload or confirm) to enable the client to
+ * handle the dialog in their own way. AwContentsClient will offer an object
+ * that implements this interface to the client and when the client has handled
+ * the dialog, it must either callback with confirm() or cancel() to allow
+ * processing to continue.
+ */
+public interface JsResultReceiver {
+ public void confirm();
+ public void cancel();
+}
diff --git a/src/org/chromium/android_webview/SslUtil.java b/src/org/chromium/android_webview/SslUtil.java
new file mode 100644
index 0000000..41b4c18
--- /dev/null
+++ b/src/org/chromium/android_webview/SslUtil.java
@@ -0,0 +1,62 @@
+// Copyright (c) 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+package org.chromium.android_webview;
+
+import android.net.http.SslCertificate;
+import android.net.http.SslError;
+import android.util.Log;
+
+import org.chromium.net.NetError;
+import org.chromium.net.X509Util;
+
+import java.security.KeyStoreException;
+import java.security.NoSuchAlgorithmException;
+import java.security.cert.CertificateException;
+import java.security.cert.X509Certificate;
+
+public class SslUtil {
+ private static final String TAG = SslUtil.class.getSimpleName();
+
+ /**
+ * Creates an SslError object from a chromium net error code.
+ */
+ public static SslError sslErrorFromNetErrorCode(int error, SslCertificate cert, String url) {
+ assert (error >= NetError.ERR_CERT_END && error <= NetError.ERR_CERT_COMMON_NAME_INVALID);
+ switch(error) {
+ case NetError.ERR_CERT_COMMON_NAME_INVALID:
+ return new SslError(SslError.SSL_IDMISMATCH, cert, url);
+ case NetError.ERR_CERT_DATE_INVALID:
+ return new SslError(SslError.SSL_DATE_INVALID, cert, url);
+ case NetError.ERR_CERT_AUTHORITY_INVALID:
+ return new SslError(SslError.SSL_UNTRUSTED, cert, url);
+ default:
+ break;
+ }
+ // Map all other codes to SSL_INVALID.
+ return new SslError(SslError.SSL_INVALID, cert, url);
+ }
+
+ public static SslCertificate getCertificateFromDerBytes(byte[] derBytes) {
+ if (derBytes == null) {
+ return null;
+ }
+
+ try {
+ X509Certificate x509Certificate =
+ X509Util.createCertificateFromBytes(derBytes);
+ return new SslCertificate(x509Certificate);
+ } catch (CertificateException e) {
+ // A SSL related exception must have occured. This shouldn't happen.
+ Log.w(TAG, "Could not read certificate: " + e);
+ } catch (KeyStoreException e) {
+ // A SSL related exception must have occured. This shouldn't happen.
+ Log.w(TAG, "Could not read certificate: " + e);
+ } catch (NoSuchAlgorithmException e) {
+ // A SSL related exception must have occured. This shouldn't happen.
+ Log.w(TAG, "Could not read certificate: " + e);
+ }
+ return null;
+ }
+}
\ No newline at end of file
diff --git a/src/org/chromium/base/AccessedByNative.java b/src/org/chromium/base/AccessedByNative.java
new file mode 100644
index 0000000..0a73258
--- /dev/null
+++ b/src/org/chromium/base/AccessedByNative.java
@@ -0,0 +1,20 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+package org.chromium.base;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * @AccessedByNative is used to ensure proguard will keep this field, since it's
+ * only accessed by native.
+ */
+@Target(ElementType.FIELD)
+@Retention(RetentionPolicy.CLASS)
+public @interface AccessedByNative {
+ public String value() default "";
+}
diff --git a/src/org/chromium/base/ActivityState.java b/src/org/chromium/base/ActivityState.java
new file mode 100644
index 0000000..c3c8980
--- /dev/null
+++ b/src/org/chromium/base/ActivityState.java
@@ -0,0 +1,9 @@
+package org.chromium.base;
+interface ActivityState {
+public final int CREATED = 1;
+public final int STARTED = 2;
+public final int RESUMED = 3;
+public final int PAUSED = 4;
+public final int STOPPED = 5;
+public final int DESTROYED = 6;
+}
diff --git a/src/org/chromium/base/ActivityState.template b/src/org/chromium/base/ActivityState.template
new file mode 100644
index 0000000..adf990a
--- /dev/null
+++ b/src/org/chromium/base/ActivityState.template
@@ -0,0 +1,14 @@
+// Copyright (c) 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+package org.chromium.base;
+
+// A simple auto-generated interface used to list the various
+// states of an activity as used by both org.chromium.base.ActivityStatus
+// and base/android/activity_status.h
+interface ActivityState {
+#define DEFINE_ACTIVITY_STATE(x,y) public final int x = y;
+#include "base/android/activity_state_list.h"
+#undef DEFINE_ACTIVITY_STATE
+}
diff --git a/src/org/chromium/base/ActivityStatus.java b/src/org/chromium/base/ActivityStatus.java
new file mode 100644
index 0000000..4747234
--- /dev/null
+++ b/src/org/chromium/base/ActivityStatus.java
@@ -0,0 +1,136 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+package org.chromium.base;
+
+import android.app.Activity;
+import android.os.Handler;
+import android.os.Looper;
+
+/**
+ * Provides information about the current activity's status, and a way
+ * to register / unregister listeners for state changes.
+ */
+@JNINamespace("base::android")
+public class ActivityStatus {
+
+ // Constants matching activity states reported to StateListener.onStateChange
+ // As an implementation detail, these are now defined in the auto-generated
+ // ActivityState interface, to be shared with C++.
+ public static final int CREATED = ActivityState.CREATED;
+ public static final int STARTED = ActivityState.STARTED;
+ public static final int RESUMED = ActivityState.RESUMED;
+ public static final int PAUSED = ActivityState.PAUSED;
+ public static final int STOPPED = ActivityState.STOPPED;
+ public static final int DESTROYED = ActivityState.DESTROYED;
+
+ // Current main activity, or null if none.
+ private static Activity sActivity;
+
+ // Current main activity's state. This can be set even if sActivity is null, to simplify unit
+ // testing.
+ private static int sActivityState;
+
+ private static final ObserverList sStateListeners =
+ new ObserverList();
+
+ /**
+ * Interface to be implemented by listeners.
+ */
+ public interface StateListener {
+ /**
+ * Called when the activity's state changes.
+ * @param newState New activity state.
+ */
+ public void onActivityStateChange(int newState);
+ }
+
+ private ActivityStatus() {}
+
+ /**
+ * Must be called by the main activity when it changes state.
+ * @param activity Current activity.
+ * @param newState New state value.
+ */
+ public static void onStateChange(Activity activity, int newState) {
+ if (sActivity != activity) {
+ // ActivityStatus is notified with the CREATED event very late during the main activity
+ // creation to avoid making startup performance worse than it is by notifying observers
+ // that could do some expensive work. This can lead to non-CREATED events being fired
+ // before the CREATED event which is problematic.
+ // TODO(pliard): fix http://crbug.com/176837.
+ sActivity = activity;
+ }
+ sActivityState = newState;
+ for (StateListener listener : sStateListeners) {
+ listener.onActivityStateChange(newState);
+ }
+ if (newState == DESTROYED) {
+ sActivity = null;
+ }
+ }
+
+ /**
+ * Indicates that the parent activity is currently paused.
+ */
+ public static boolean isPaused() {
+ return sActivityState == PAUSED;
+ }
+
+ /**
+ * Returns the current main application activity.
+ */
+ public static Activity getActivity() {
+ return sActivity;
+ }
+
+ /**
+ * Returns the current main application activity's state.
+ */
+ public static int getState() {
+ return sActivityState;
+ }
+
+ /**
+ * Registers the given listener to receive activity state changes.
+ * @param listener Listener to receive state changes.
+ */
+ public static void registerStateListener(StateListener listener) {
+ sStateListeners.addObserver(listener);
+ }
+
+ /**
+ * Unregisters the given listener from receiving activity state changes.
+ * @param listener Listener that doesn't want to receive state changes.
+ */
+ public static void unregisterStateListener(StateListener listener) {
+ sStateListeners.removeObserver(listener);
+ }
+
+ /**
+ * Registers the single thread-safe native activity status listener.
+ * This handles the case where the caller is not on the main thread.
+ * Note that this is used by a leaky singleton object from the native
+ * side, hence lifecycle management is greatly simplified.
+ */
+ @CalledByNative
+ private static void registerThreadSafeNativeStateListener() {
+ ThreadUtils.runOnUiThread(new Runnable () {
+ @Override
+ public void run() {
+ // Register a new listener that calls nativeOnActivityStateChange.
+ sStateListeners.addObserver(new StateListener() {
+ @Override
+ public void onActivityStateChange(int newState) {
+ nativeOnActivityStateChange(newState);
+ }
+ });
+ }
+ });
+ }
+
+ // Called to notify the native side of state changes.
+ // IMPORTANT: This is always called on the main thread!
+ private static native void nativeOnActivityStateChange(int newState);
+}
diff --git a/src/org/chromium/base/BuildInfo.java b/src/org/chromium/base/BuildInfo.java
new file mode 100644
index 0000000..2314051
--- /dev/null
+++ b/src/org/chromium/base/BuildInfo.java
@@ -0,0 +1,114 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+package org.chromium.base;
+
+import android.content.Context;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageInfo;
+import android.content.pm.PackageManager;
+import android.content.pm.PackageManager.NameNotFoundException;
+import android.os.Build;
+import android.util.Log;
+
+import org.chromium.base.CalledByNative;
+
+/**
+ * BuildInfo is a utility class providing easy access to {@link PackageInfo}
+ * information. This is primarly of use for accessesing package information
+ * from native code.
+ */
+public class BuildInfo {
+ private static final String TAG = "BuildInfo";
+ private static final int MAX_FINGERPRINT_LENGTH = 128;
+
+ /**
+ * BuildInfo is a static utility class and therefore shouldn't be
+ * instantiated.
+ */
+ private BuildInfo() {
+ }
+
+ @CalledByNative
+ public static String getDevice() {
+ return Build.DEVICE;
+ }
+
+ @CalledByNative
+ public static String getBrand() {
+ return Build.BRAND;
+ }
+
+ @CalledByNative
+ public static String getAndroidBuildId() {
+ return Build.ID;
+ }
+
+ /**
+ * @return The build fingerprint for the current Android install. The value is truncated to a
+ * 128 characters as this is used for crash and UMA reporting, which should avoid huge
+ * strings.
+ */
+ @CalledByNative
+ public static String getAndroidBuildFingerprint() {
+ return Build.FINGERPRINT.substring(
+ 0, Math.min(Build.FINGERPRINT.length(), MAX_FINGERPRINT_LENGTH));
+ }
+
+ @CalledByNative
+ public static String getDeviceModel() {
+ return Build.MODEL;
+ }
+
+ @CalledByNative
+ public static String getPackageVersionCode(Context context) {
+ String msg = "versionCode not available.";
+ try {
+ PackageManager pm = context.getPackageManager();
+ PackageInfo pi = pm.getPackageInfo(context.getPackageName(), 0);
+ msg = "" + pi.versionCode;
+ } catch (NameNotFoundException e) {
+ Log.d(TAG, msg);
+ }
+ return msg;
+
+ }
+
+ @CalledByNative
+ public static String getPackageVersionName(Context context) {
+ String msg = "versionName not available";
+ try {
+ PackageManager pm = context.getPackageManager();
+ PackageInfo pi = pm.getPackageInfo(context.getPackageName(), 0);
+ msg = pi.versionName;
+ } catch (NameNotFoundException e) {
+ Log.d(TAG, msg);
+ }
+ return msg;
+ }
+
+ @CalledByNative
+ public static String getPackageLabel(Context context) {
+ try {
+ PackageManager packageManager = context.getPackageManager();
+ ApplicationInfo appInfo = packageManager.getApplicationInfo(context.getPackageName(),
+ PackageManager.GET_META_DATA);
+ CharSequence label = packageManager.getApplicationLabel(appInfo);
+ return label != null ? label.toString() : "";
+ } catch (NameNotFoundException e) {
+ return "";
+ }
+ }
+
+ @CalledByNative
+ public static String getPackageName(Context context) {
+ String packageName = context != null ? context.getPackageName() : null;
+ return packageName != null ? packageName : "";
+ }
+
+ @CalledByNative
+ public static int getSdkInt() {
+ return Build.VERSION.SDK_INT;
+ }
+}
diff --git a/src/org/chromium/base/CalledByNative.java b/src/org/chromium/base/CalledByNative.java
new file mode 100644
index 0000000..db01b0d
--- /dev/null
+++ b/src/org/chromium/base/CalledByNative.java
@@ -0,0 +1,23 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+package org.chromium.base;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * @CalledByNative is used by the JNI generator to create the necessary JNI
+ * bindings and expose this method to native code.
+ */
+@Target(ElementType.METHOD)
+@Retention(RetentionPolicy.CLASS)
+public @interface CalledByNative {
+ /*
+ * If present, tells which inner class the method belongs to.
+ */
+ public String value() default "";
+}
diff --git a/src/org/chromium/base/CalledByNativeUnchecked.java b/src/org/chromium/base/CalledByNativeUnchecked.java
new file mode 100644
index 0000000..38bb0c0
--- /dev/null
+++ b/src/org/chromium/base/CalledByNativeUnchecked.java
@@ -0,0 +1,27 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+package org.chromium.base;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * @CalledByNativeUnchecked is used to generate JNI bindings that do not check for exceptions.
+ * It only makes sense to use this annotation on methods that declare a throws... spec.
+ * However, note that the exception received native side maybe an 'unchecked' (RuntimeExpception)
+ * such as NullPointerException, so the native code should differentiate these cases.
+ * Usage of this should be very rare; where possible handle exceptions in the Java side and use a
+ * return value to indicate success / failure.
+ */
+@Target(ElementType.METHOD)
+@Retention(RetentionPolicy.CLASS)
+public @interface CalledByNativeUnchecked {
+ /*
+ * If present, tells which inner class the method belongs to.
+ */
+ public String value() default "";
+}
diff --git a/src/org/chromium/base/ChromiumActivity.java b/src/org/chromium/base/ChromiumActivity.java
new file mode 100644
index 0000000..65f5ce9
--- /dev/null
+++ b/src/org/chromium/base/ChromiumActivity.java
@@ -0,0 +1,49 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+package org.chromium.base;
+
+import android.app.Activity;
+import android.os.Bundle;
+
+// All Chromium main activities should extend this class. This allows various sub-systems to
+// properly react to activity state changes.
+public class ChromiumActivity extends Activity {
+
+ @Override
+ protected void onCreate(Bundle savedInstance) {
+ super.onCreate(savedInstance);
+ ActivityStatus.onStateChange(this, ActivityStatus.CREATED);
+ }
+
+ @Override
+ protected void onStart() {
+ super.onStart();
+ ActivityStatus.onStateChange(this, ActivityStatus.STARTED);
+ }
+
+ @Override
+ protected void onResume() {
+ super.onResume();
+ ActivityStatus.onStateChange(this, ActivityStatus.RESUMED);
+ }
+
+ @Override
+ protected void onPause() {
+ ActivityStatus.onStateChange(this, ActivityStatus.PAUSED);
+ super.onPause();
+ }
+
+ @Override
+ protected void onStop() {
+ ActivityStatus.onStateChange(this, ActivityStatus.STOPPED);
+ super.onStop();
+ }
+
+ @Override
+ protected void onDestroy() {
+ ActivityStatus.onStateChange(this, ActivityStatus.DESTROYED);
+ super.onDestroy();
+ }
+}
diff --git a/src/org/chromium/base/ContextTypes.java b/src/org/chromium/base/ContextTypes.java
new file mode 100644
index 0000000..35ecd8f
--- /dev/null
+++ b/src/org/chromium/base/ContextTypes.java
@@ -0,0 +1,96 @@
+// Copyright (c) 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+package org.chromium.base;
+
+import android.content.Context;
+
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.Map;
+
+/**
+ * Maintains the {@link Context}-to-"context type" mapping. The context type
+ * {@code MODE_APP} is chosen for the application context associated with
+ * the activity running in application mode, while {@code MODE_NORMAL} for main
+ * Chromium activity.
+ *
+ *
Used as singleton instance.
+ */
+public class ContextTypes {
+
+ // Available context types.
+ public static final int CONTEXT_TYPE_NORMAL = 1;
+ public static final int CONTEXT_TYPE_WEBAPP = 2;
+
+ private final Map mContextMap;
+
+ private ContextTypes() {
+ mContextMap = new ConcurrentHashMap();
+ }
+
+ private static class ContextTypesHolder {
+ private static final ContextTypes INSTANCE = new ContextTypes();
+ }
+
+ public static ContextTypes getInstance() {
+ return ContextTypesHolder.INSTANCE;
+ }
+
+ /**
+ * Adds the mapping for the given {@link Context}.
+ *
+ * @param context {@link Context} in interest
+ * @param type the type associated with the context
+ * @throws IllegalArgumentException if type is not a valid one.
+ */
+ public void put(Context context, int type) throws IllegalArgumentException {
+ if (type != CONTEXT_TYPE_NORMAL && type != CONTEXT_TYPE_WEBAPP) {
+ throw new IllegalArgumentException("Wrong context type");
+ }
+ mContextMap.put(context, type);
+ }
+
+ /**
+ * Removes the mapping for the given context.
+ *
+ * @param context {@link Context} in interest
+ */
+ public void remove(Context context) {
+ mContextMap.remove(context);
+ }
+
+ /**
+ * Returns type of the given context.
+ *
+ * @param context {@link Context} in interest
+ * @return type associated with the context. Returns {@code MODE_NORMAL} by
+ * default if the mapping for the queried context is not present.
+ */
+ public int getType(Context context) {
+ Integer contextType = mContextMap.get(context);
+ return contextType == null ? CONTEXT_TYPE_NORMAL : contextType;
+ }
+
+ /**
+ * Returns whether activity is running in web app mode.
+ *
+ * @param appContext {@link Context} in interest
+ * @return {@code true} when activity is running in web app mode.
+ */
+ @CalledByNative
+ public static boolean isRunningInWebapp(Context appContext) {
+ return ContextTypes.getInstance().getType(appContext)
+ == CONTEXT_TYPE_WEBAPP;
+ }
+
+ /**
+ * Checks if the mapping exists for the given context.
+ *
+ * @param context {@link Context} in interest
+ * @return {@code true} if the mapping exists; otherwise {@code false}
+ */
+ public boolean contains(Context context) {
+ return mContextMap.containsKey(context);
+ }
+}
diff --git a/src/org/chromium/base/CpuFeatures.java b/src/org/chromium/base/CpuFeatures.java
new file mode 100644
index 0000000..f298fb1
--- /dev/null
+++ b/src/org/chromium/base/CpuFeatures.java
@@ -0,0 +1,40 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+package org.chromium.base;
+
+// The only purpose of this class is to allow sending CPU properties
+// from the browser process to sandboxed renderer processes. This is
+// needed because sandboxed processes cannot, on ARM, query the kernel
+// about the CPU's properties by parsing /proc, so this operation must
+// be performed in the browser process, and the result passed to
+// renderer ones.
+//
+// For more context, see http://crbug.com/164154
+//
+// Technically, this is a wrapper around the native NDK cpufeatures
+// library. The exact CPU features bits are never used in Java so
+// there is no point in duplicating their definitions here.
+//
+@JNINamespace("base::android")
+public abstract class CpuFeatures {
+ /**
+ * Return the number of CPU Cores on the device.
+ */
+ public static int getCount() {
+ return nativeGetCoreCount();
+ }
+
+ /**
+ * Return the CPU feature mask.
+ * This is a 64-bit integer that corresponds to the CPU's features.
+ * The value comes directly from android_getCpuFeatures().
+ */
+ public static long getMask() {
+ return nativeGetCpuFeatures();
+ }
+
+ private static native int nativeGetCoreCount();
+ private static native long nativeGetCpuFeatures();
+}
diff --git a/src/org/chromium/base/ImportantFileWriterAndroid.java b/src/org/chromium/base/ImportantFileWriterAndroid.java
new file mode 100644
index 0000000..1c7c018
--- /dev/null
+++ b/src/org/chromium/base/ImportantFileWriterAndroid.java
@@ -0,0 +1,29 @@
+// Copyright (c) 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+package org.chromium.base;
+
+/**
+ * This class provides an interface to the native class for writing
+ * important data files without risking data loss.
+ */
+@JNINamespace("base::android")
+public class ImportantFileWriterAndroid {
+
+ /**
+ * Write a binary file atomically.
+ *
+ * This either writes all the data or leaves the file unchanged.
+ *
+ * @param fileName The complete path of the file to be written
+ * @param data The data to be written to the file
+ * @return true if the data was written to the file, false if not.
+ */
+ public static boolean writeFileAtomically(String fileName, byte[] data) {
+ return nativeWriteFileAtomically(fileName, data);
+ }
+
+ private static native boolean nativeWriteFileAtomically(
+ String fileName, byte[] data);
+}
diff --git a/src/org/chromium/base/JNINamespace.java b/src/org/chromium/base/JNINamespace.java
new file mode 100644
index 0000000..cfffc91
--- /dev/null
+++ b/src/org/chromium/base/JNINamespace.java
@@ -0,0 +1,20 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+package org.chromium.base;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * @JNINamespace is used by the JNI generator to create the necessary JNI
+ * bindings and expose this method to native code using the specified namespace.
+ */
+@Target(ElementType.TYPE)
+@Retention(RetentionPolicy.RUNTIME)
+public @interface JNINamespace {
+ public String value();
+}
diff --git a/src/org/chromium/base/NativeClassQualifiedName.java b/src/org/chromium/base/NativeClassQualifiedName.java
new file mode 100644
index 0000000..309169b
--- /dev/null
+++ b/src/org/chromium/base/NativeClassQualifiedName.java
@@ -0,0 +1,25 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+package org.chromium.base;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * @NativeClassQualifiedName is used by the JNI generator to create the necessary JNI
+ * bindings to call into the specified native class name.
+ */
+@Target(ElementType.METHOD)
+@Retention(RetentionPolicy.RUNTIME)
+public @interface NativeClassQualifiedName {
+ /*
+ * Tells which native class the method is going to be bound to.
+ * The first parameter of the annotated method must be an int nativePtr pointing to
+ * an instance of this class.
+ */
+ public String value();
+}
diff --git a/src/org/chromium/base/ObserverList.java b/src/org/chromium/base/ObserverList.java
new file mode 100644
index 0000000..13a81c5
--- /dev/null
+++ b/src/org/chromium/base/ObserverList.java
@@ -0,0 +1,177 @@
+// Copyright 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+package org.chromium.base;
+
+import java.lang.Iterable;
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.List;
+import java.util.NoSuchElementException;
+
+import javax.annotation.concurrent.NotThreadSafe;
+
+/**
+ * A container for a list of observers.
+ *
+ * This container can be modified during iteration without invalidating the iterator.
+ * So, it safely handles the case of an observer removing itself or other observers from the list
+ * while observers are being notified.
+ *
+ * The implementation (and the interface) is heavily influenced by the C++ ObserverList.
+ * Notable differences:
+ * - The iterator implements NOTIFY_EXISTING_ONLY.
+ * - The FOR_EACH_OBSERVER closure is left to the clients to implement in terms of iterator().
+ *
+ * This class is not threadsafe. Observers MUST be added, removed and will be notified on the same
+ * thread this is created.
+ */
+@NotThreadSafe
+public class ObserverList implements Iterable {
+ public final List mObservers = new ArrayList();
+ private int mIterationDepth = 0;
+
+ public ObserverList() {}
+
+ /**
+ * Add an observer to the list.
+ *
+ * An observer should not be added to the same list more than once. If an iteration is already
+ * in progress, this observer will be not be visible during that iteration.
+ */
+ public void addObserver(E obs) {
+ // Avoid adding null elements to the list as they may be removed on a compaction.
+ if (obs == null || mObservers.contains(obs)) {
+ assert false;
+ return;
+ }
+
+ // Structurally modifying the underlying list here. This means we
+ // cannot use the underlying list's iterator to iterate over the list.
+ mObservers.add(obs);
+ }
+
+ /**
+ * Remove an observer from the list if it is in the list.
+ */
+ public void removeObserver(E obs) {
+ int index = mObservers.indexOf(obs);
+
+ if (index == -1)
+ return;
+
+ if (mIterationDepth == 0) {
+ // No one is iterating over the list.
+ mObservers.remove(obs);
+ } else {
+ mObservers.set(index, null);
+ }
+ }
+
+ public boolean hasObserver(E obs) {
+ return mObservers.contains(obs);
+ }
+
+ public void clear() {
+ if (mIterationDepth == 0) {
+ mObservers.clear();
+ return;
+ }
+
+ int size = mObservers.size();
+ for (int i = 0; i < size; i++)
+ mObservers.set(i, null);
+ }
+
+ @Override
+ public Iterator iterator() {
+ return new ObserverListIterator();
+ }
+
+ /**
+ * Compact the underlying list be removing null elements.
+ *
+ * Should only be called when mIterationDepth is zero.
+ */
+ private void compact() {
+ assert mIterationDepth == 0;
+ // Safe to use the underlying list's iterator, as we know that no-one else
+ // is iterating over the list.
+ Iterator it = mObservers.iterator();
+ while (it.hasNext()) {
+ E el = it.next();
+ if (el == null)
+ it.remove();
+ }
+ }
+
+ private void incrementIterationDepth() {
+ mIterationDepth++;
+ }
+
+ private void decrementIterationDepthAndCompactIfNeeded() {
+ mIterationDepth--;
+ assert mIterationDepth >= 0;
+ if (mIterationDepth == 0)
+ compact();
+ }
+
+ private int getSize() {
+ return mObservers.size();
+ }
+
+ private E getObserverAt(int index) {
+ return mObservers.get(index);
+ }
+
+ private class ObserverListIterator implements Iterator {
+ private final int mListEndMarker;
+ private int mIndex = 0;
+ private boolean mIsExhausted = false;
+
+ private ObserverListIterator() {
+ ObserverList.this.incrementIterationDepth();
+ mListEndMarker = ObserverList.this.getSize();
+ }
+
+ @Override
+ public boolean hasNext() {
+ int lookupIndex = mIndex;
+ while (lookupIndex < mListEndMarker &&
+ ObserverList.this.getObserverAt(lookupIndex) == null)
+ lookupIndex++;
+ if (lookupIndex < mListEndMarker)
+ return true;
+
+ // We have reached the end of the list, allow for compaction.
+ compactListIfNeeded();
+ return false;
+ }
+
+ @Override
+ public E next() {
+ // Advance if the current element is null.
+ while (mIndex < mListEndMarker && ObserverList.this.getObserverAt(mIndex) == null)
+ mIndex++;
+ if (mIndex < mListEndMarker)
+ return ObserverList.this.getObserverAt(mIndex++);
+
+ // We have reached the end of the list, allow for compaction.
+ compactListIfNeeded();
+ throw new NoSuchElementException();
+ }
+
+ @Override
+ public void remove() {
+ throw new UnsupportedOperationException();
+ }
+
+ private void compactListIfNeeded() {
+ if (!mIsExhausted) {
+ mIsExhausted = true;
+ ObserverList.this.decrementIterationDepthAndCompactIfNeeded();
+ }
+ }
+ }
+}
diff --git a/src/org/chromium/base/PathService.java b/src/org/chromium/base/PathService.java
new file mode 100644
index 0000000..dfda736
--- /dev/null
+++ b/src/org/chromium/base/PathService.java
@@ -0,0 +1,24 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+package org.chromium.base;
+
+/**
+ * This class provides java side access to the native PathService.
+ */
+@JNINamespace("base::android")
+public abstract class PathService {
+
+ // Must match the value of DIR_MODULE in base/base_paths.h!
+ public static final int DIR_MODULE = 3;
+
+ // Prevent instantiation.
+ private PathService() {}
+
+ public static void override(int what, String path) {
+ nativeOverride(what, path);
+ }
+
+ private static native void nativeOverride(int what, String path);
+}
diff --git a/src/org/chromium/base/PathUtils.java b/src/org/chromium/base/PathUtils.java
new file mode 100644
index 0000000..aee5c05
--- /dev/null
+++ b/src/org/chromium/base/PathUtils.java
@@ -0,0 +1,108 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+package org.chromium.base;
+
+import android.content.Context;
+import android.content.pm.ApplicationInfo;
+import android.os.Environment;
+
+import java.io.File;
+
+/**
+ * This class provides the path related methods for the native library.
+ */
+public abstract class PathUtils {
+
+ private static String sDataDirectorySuffix;
+ private static String sWebappDirectorySuffix;
+ private static String sWebappCacheDirectory;
+
+ // Prevent instantiation.
+ private PathUtils() {}
+
+ /**
+ * Sets the suffix that should be used for the directory where private data is to be stored
+ * by the application.
+ * @param suffix The private data directory suffix.
+ * @see Context#getDir(String, int)
+ */
+ public static void setPrivateDataDirectorySuffix(String suffix) {
+ sDataDirectorySuffix = suffix;
+ }
+
+ /**
+ * Sets the directory info used for chrome process running in application mode.
+ *
+ * @param webappSuffix The suffix of the directory used for storing webapp-specific profile
+ * @param cacheDir Cache directory name for web apps.
+ */
+ public static void setWebappDirectoryInfo(String webappSuffix, String cacheDir) {
+ sWebappDirectorySuffix = webappSuffix;
+ sWebappCacheDirectory = cacheDir;
+ }
+
+ /**
+ * @return the private directory that is used to store application data.
+ */
+ @CalledByNative
+ public static String getDataDirectory(Context appContext) {
+ if (sDataDirectorySuffix == null) {
+ throw new IllegalStateException(
+ "setDataDirectorySuffix must be called before getDataDirectory");
+ }
+ return appContext.getDir(sDataDirectorySuffix, Context.MODE_PRIVATE).getPath();
+ }
+
+ /**
+ * @return the cache directory.
+ */
+ @SuppressWarnings("unused")
+ @CalledByNative
+ public static String getCacheDirectory(Context appContext) {
+ if (ContextTypes.getInstance().getType(appContext) == ContextTypes.CONTEXT_TYPE_NORMAL) {
+ return appContext.getCacheDir().getPath();
+ }
+ if (sWebappDirectorySuffix == null || sWebappCacheDirectory == null) {
+ throw new IllegalStateException(
+ "setWebappDirectoryInfo must be called before getCacheDirectory");
+ }
+ return new File(appContext.getDir(sWebappDirectorySuffix, appContext.MODE_PRIVATE),
+ sWebappCacheDirectory).getPath();
+ }
+
+ /**
+ * @return the public downloads directory.
+ */
+ @SuppressWarnings("unused")
+ @CalledByNative
+ private static String getDownloadsDirectory(Context appContext) {
+ return Environment.getExternalStoragePublicDirectory(
+ Environment.DIRECTORY_DOWNLOADS).getPath();
+ }
+
+ /**
+ * @return the path to native libraries.
+ */
+ @SuppressWarnings("unused")
+ @CalledByNative
+ private static String getNativeLibraryDirectory(Context appContext) {
+ ApplicationInfo ai = appContext.getApplicationInfo();
+ if ((ai.flags & ApplicationInfo.FLAG_UPDATED_SYSTEM_APP) != 0 ||
+ (ai.flags & ApplicationInfo.FLAG_SYSTEM) == 0) {
+ return ai.nativeLibraryDir;
+ }
+
+ return "/system/lib/";
+ }
+
+ /**
+ * @return the external storage directory.
+ */
+ @SuppressWarnings("unused")
+ @CalledByNative
+ public static String getExternalStorageDirectory() {
+ return Environment.getExternalStorageDirectory().getAbsolutePath();
+ }
+}
diff --git a/src/org/chromium/base/PowerMonitor.java b/src/org/chromium/base/PowerMonitor.java
new file mode 100644
index 0000000..b7a691e
--- /dev/null
+++ b/src/org/chromium/base/PowerMonitor.java
@@ -0,0 +1,92 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+package org.chromium.base;
+
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.os.BatteryManager;
+import android.os.Handler;
+import android.os.Looper;
+
+
+/**
+ * Integrates native PowerMonitor with the java side.
+ */
+@JNINamespace("base::android")
+public class PowerMonitor implements ActivityStatus.StateListener {
+ private static final long SUSPEND_DELAY_MS = 1 * 60 * 1000; // 1 minute.
+ private static class LazyHolder {
+ private static final PowerMonitor INSTANCE = new PowerMonitor();
+ }
+ private static PowerMonitor sInstance;
+
+ private boolean mIsBatteryPower;
+ private final Handler mHandler = new Handler(Looper.getMainLooper());
+
+ // Asynchronous task used to fire the "paused" event to the native side 1 minute after the main
+ // activity transitioned to the "paused" state. This event is not sent immediately because it
+ // would be too aggressive. An Android activity can be in the "paused" state quite often. This
+ // can happen when a dialog window shows up for instance.
+ private static final Runnable sSuspendTask = new Runnable() {
+ @Override
+ public void run() {
+ nativeOnMainActivitySuspended();
+ }
+ };
+
+ public static void createForTests(Context context) {
+ // Applications will create this once the JNI side has been fully wired up both sides. For
+ // tests, we just need native -> java, that is, we don't need to notify java -> native on
+ // creation.
+ sInstance = LazyHolder.INSTANCE;
+ }
+
+ public static void create(Context context) {
+ if (sInstance == null) {
+ sInstance = LazyHolder.INSTANCE;
+ ActivityStatus.registerStateListener(sInstance);
+ IntentFilter ifilter = new IntentFilter(Intent.ACTION_BATTERY_CHANGED);
+ Intent batteryStatusIntent = context.registerReceiver(null, ifilter);
+ onBatteryChargingChanged(batteryStatusIntent);
+ }
+ }
+
+ private PowerMonitor() {
+ }
+
+ public static void onBatteryChargingChanged(Intent intent) {
+ if (sInstance == null) {
+ // We may be called by the framework intent-filter before being fully initialized. This
+ // is not a problem, since our constructor will check for the state later on.
+ return;
+ }
+ int chargePlug = intent.getIntExtra(BatteryManager.EXTRA_PLUGGED, -1);
+ // If we're not plugged, assume we're running on battery power.
+ sInstance.mIsBatteryPower = chargePlug != BatteryManager.BATTERY_PLUGGED_USB &&
+ chargePlug != BatteryManager.BATTERY_PLUGGED_AC;
+ nativeOnBatteryChargingChanged();
+ }
+
+ @Override
+ public void onActivityStateChange(int newState) {
+ if (newState == ActivityStatus.RESUMED) {
+ // Remove the callback from the message loop in case it hasn't been executed yet.
+ mHandler.removeCallbacks(sSuspendTask);
+ nativeOnMainActivityResumed();
+ } else if (newState == ActivityStatus.PAUSED) {
+ mHandler.postDelayed(sSuspendTask, SUSPEND_DELAY_MS);
+ }
+ }
+
+ @CalledByNative
+ private static boolean isBatteryPower() {
+ return sInstance.mIsBatteryPower;
+ }
+
+ private static native void nativeOnBatteryChargingChanged();
+ private static native void nativeOnMainActivitySuspended();
+ private static native void nativeOnMainActivityResumed();
+}
diff --git a/src/org/chromium/base/PowerStatusReceiver.java b/src/org/chromium/base/PowerStatusReceiver.java
new file mode 100644
index 0000000..f36c146
--- /dev/null
+++ b/src/org/chromium/base/PowerStatusReceiver.java
@@ -0,0 +1,23 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+package org.chromium.base;
+
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+
+
+/**
+ * A BroadcastReceiver that listens to changes in power status and notifies
+ * PowerMonitor.
+ * It's instantiated by the framework via the application intent-filter
+ * declared in its manifest.
+ */
+public class PowerStatusReceiver extends BroadcastReceiver {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ PowerMonitor.onBatteryChargingChanged(intent);
+ }
+}
diff --git a/src/org/chromium/base/SystemMessageHandler.java b/src/org/chromium/base/SystemMessageHandler.java
new file mode 100644
index 0000000..f7bb19f
--- /dev/null
+++ b/src/org/chromium/base/SystemMessageHandler.java
@@ -0,0 +1,93 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+package org.chromium.base;
+
+import android.os.Handler;
+import android.os.Message;
+import android.os.SystemClock;
+
+import java.util.concurrent.atomic.AtomicBoolean;
+
+class SystemMessageHandler extends Handler {
+
+ private static final int TIMER_MESSAGE = 1;
+ private static final int DELAYED_TIMER_MESSAGE = 2;
+
+ // Native class pointer set by the constructor of the SharedClient native class.
+ private int mMessagePumpDelegateNative = 0;
+
+ // Used to ensure we have at most one TIMER_MESSAGE pending at once.
+ private AtomicBoolean mTimerFired = new AtomicBoolean(true);
+
+ // Used to insert TIMER_MESSAGE on the front of the system message queue during startup only.
+ // This is a wee hack, to give a priority boost to native tasks during startup as they tend to
+ // be on the critical path. (After startup, handling the UI with minimum latency is more
+ // important).
+ private boolean mStartupComplete = false;
+ private final long mStartupCompleteTime = System.currentTimeMillis() + 2000;
+ private final boolean startupComplete() {
+ if (!mStartupComplete && System.currentTimeMillis() > mStartupCompleteTime) {
+ mStartupComplete = true;
+ }
+ return mStartupComplete;
+ }
+
+ private SystemMessageHandler(int messagePumpDelegateNative) {
+ mMessagePumpDelegateNative = messagePumpDelegateNative;
+ }
+
+ @Override
+ public void handleMessage(Message msg) {
+ if (msg.what == TIMER_MESSAGE) {
+ mTimerFired.set(true);
+ }
+ while (nativeDoRunLoopOnce(mMessagePumpDelegateNative)) {
+ if (startupComplete()) {
+ setTimer();
+ break;
+ }
+ }
+ }
+
+ @CalledByNative
+ private void setTimer() {
+ if (!mTimerFired.getAndSet(false)) {
+ // mTimerFired was already false.
+ return;
+ }
+ if (startupComplete()) {
+ sendEmptyMessage(TIMER_MESSAGE);
+ } else {
+ sendMessageAtFrontOfQueue(obtainMessage(TIMER_MESSAGE));
+ }
+ }
+
+ // If millis <=0, it'll send a TIMER_MESSAGE instead of
+ // a DELAYED_TIMER_MESSAGE.
+ @SuppressWarnings("unused")
+ @CalledByNative
+ private void setDelayedTimer(long millis) {
+ if (millis <= 0) {
+ setTimer();
+ } else {
+ removeMessages(DELAYED_TIMER_MESSAGE);
+ sendEmptyMessageDelayed(DELAYED_TIMER_MESSAGE, millis);
+ }
+ }
+
+ @SuppressWarnings("unused")
+ @CalledByNative
+ private void removeTimer() {
+ removeMessages(TIMER_MESSAGE);
+ removeMessages(DELAYED_TIMER_MESSAGE);
+ }
+
+ @CalledByNative
+ private static SystemMessageHandler create(int messagePumpDelegateNative) {
+ return new SystemMessageHandler(messagePumpDelegateNative);
+ }
+
+ private native boolean nativeDoRunLoopOnce(int messagePumpDelegateNative);
+}
diff --git a/src/org/chromium/base/ThreadUtils.java b/src/org/chromium/base/ThreadUtils.java
new file mode 100644
index 0000000..cdf73c3
--- /dev/null
+++ b/src/org/chromium/base/ThreadUtils.java
@@ -0,0 +1,161 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+package org.chromium.base;
+
+import android.os.Handler;
+import android.os.Looper;
+import android.os.Process;
+
+import java.util.concurrent.Callable;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.FutureTask;
+
+/**
+ * Helper methods to deal with threading related tasks.
+ */
+public class ThreadUtils {
+
+ /**
+ * Run the supplied Runnable on the main thread. The method will block until
+ * the Runnable completes.
+ *
+ * @param r The Runnable to run.
+ */
+ public static void runOnUiThreadBlocking(final Runnable r) {
+ if (runningOnUiThread()) {
+ r.run();
+ } else {
+ FutureTask task = new FutureTask(r, null);
+ postOnUiThread(task);
+ try {
+ task.get();
+ } catch (Exception e) {
+ throw new RuntimeException("Exception occured while waiting for runnable", e);
+ }
+ }
+ }
+
+ /**
+ * Run the supplied Callable on the main thread, wrapping any exceptions in
+ * a RuntimeException. The method will block until the Callable completes.
+ *
+ * @param c The Callable to run
+ * @return The result of the callable
+ */
+ public static T runOnUiThreadBlockingNoException(Callable c) {
+ try {
+ return runOnUiThreadBlocking(c);
+ } catch (ExecutionException e) {
+ throw new RuntimeException("Error occured waiting for callable", e);
+ }
+ }
+
+ /**
+ * Run the supplied Callable on the main thread, The method will block until
+ * the Callable completes.
+ *
+ * @param c The Callable to run
+ * @return The result of the callable
+ * @throws ExecutionException c's exception
+ */
+ public static T runOnUiThreadBlocking(Callable c) throws ExecutionException {
+ FutureTask task = new FutureTask(c);
+ runOnUiThread(task);
+ try {
+ return task.get();
+ } catch (InterruptedException e) {
+ throw new RuntimeException("Interrupted waiting for callable", e);
+ }
+ }
+
+ /**
+ * Run the supplied FutureTask on the main thread. The method will block
+ * only if the current thread is the main thread.
+ *
+ * @param task The FutureTask to run
+ * @return The queried task (to aid inline construction)
+ */
+ public static FutureTask runOnUiThread(FutureTask task) {
+ if (runningOnUiThread()) {
+ task.run();
+ } else {
+ postOnUiThread(task);
+ }
+ return task;
+ }
+
+ /**
+ * Run the supplied Callable on the main thread. The method will block
+ * only if the current thread is the main thread.
+ *
+ * @param c The Callable to run
+ * @return A FutureTask wrapping the callable to retrieve results
+ */
+ public static FutureTask runOnUiThread(Callable c) {
+ return runOnUiThread(new FutureTask(c));
+ }
+
+ /**
+ * Run the supplied Runnable on the main thread. The method will block
+ * only if the current thread is the main thread.
+ *
+ * @param r The Runnable to run
+ */
+ public static void runOnUiThread(Runnable r) {
+ if (runningOnUiThread()) {
+ r.run();
+ } else {
+ LazyHolder.sUiThreadHandler.post(r);
+ }
+ }
+
+ /**
+ * Post the supplied FutureTask to run on the main thread. The method will
+ * not block, even if called on the UI thread.
+ *
+ * @param task The FutureTask to run
+ * @return The queried task (to aid inline construction)
+ */
+ public static FutureTask postOnUiThread(FutureTask task) {
+ LazyHolder.sUiThreadHandler.post(task);
+ return task;
+ }
+
+ /**
+ * Post the supplied Runnable to run on the main thread. The method will
+ * not block, even if called on the UI thread.
+ *
+ * @param task The Runnable to run
+ */
+ public static void postOnUiThread(Runnable r) {
+ LazyHolder.sUiThreadHandler.post(r);
+ }
+
+ /**
+ * Asserts that the current thread is running on the main thread.
+ */
+ public static void assertOnUiThread() {
+ assert runningOnUiThread();
+ }
+
+ /**
+ * @return true iff the current thread is the main (UI) thread.
+ */
+ public static boolean runningOnUiThread() {
+ return Looper.getMainLooper() == Looper.myLooper();
+ }
+
+ /**
+ * Set thread priority to audio.
+ */
+ @CalledByNative
+ public static void setThreadPriorityAudio(int tid) {
+ Process.setThreadPriority(tid, Process.THREAD_PRIORITY_AUDIO);
+ }
+
+ private static class LazyHolder {
+ private static Handler sUiThreadHandler = new Handler(Looper.getMainLooper());
+ }
+}
diff --git a/src/org/chromium/base/WeakContext.java b/src/org/chromium/base/WeakContext.java
new file mode 100644
index 0000000..d660cc9
--- /dev/null
+++ b/src/org/chromium/base/WeakContext.java
@@ -0,0 +1,45 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+package org.chromium.base;
+
+import android.content.Context;
+
+import java.lang.ref.WeakReference;
+import java.util.concurrent.Callable;
+
+// Holds a WeakReference to Context to allow it to be GC'd.
+// Also provides utility functions to getSystemService from the UI or any
+// other thread (may return null, if the Context has been nullified).
+public class WeakContext {
+ private static WeakReference sWeakContext;
+
+ public static void initializeWeakContext(final Context context) {
+ sWeakContext = new WeakReference(context);
+ }
+
+ public static Context getContext() {
+ return sWeakContext.get();
+ }
+
+ // Returns a system service. May be called from any thread.
+ // If necessary, it will send a message to the main thread to acquire the
+ // service, and block waiting for it to complete.
+ // May return null if context is no longer available.
+ public static Object getSystemService(final String name) {
+ final Context context = sWeakContext.get();
+ if (context == null) {
+ return null;
+ }
+ if (ThreadUtils.runningOnUiThread()) {
+ return context.getSystemService(name);
+ }
+ return ThreadUtils.runOnUiThreadBlockingNoException(new Callable