diff --git a/tests/.classpath b/tests/.classpath new file mode 100644 index 000000000..029539f51 --- /dev/null +++ b/tests/.classpath @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/tests/.project b/tests/.project new file mode 100644 index 000000000..dc7cd9aaa --- /dev/null +++ b/tests/.project @@ -0,0 +1,33 @@ + + + astrid-3.x-tests + + + + + + 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/tests/.settings/org.eclipse.jdt.core.prefs b/tests/.settings/org.eclipse.jdt.core.prefs new file mode 100644 index 000000000..3d0c11e0f --- /dev/null +++ b/tests/.settings/org.eclipse.jdt.core.prefs @@ -0,0 +1,270 @@ +#Sat Aug 22 23:48:16 PDT 2009 +eclipse.preferences.version=1 +org.eclipse.jdt.core.compiler.codegen.inlineJsrBytecode=enabled +org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.5 +org.eclipse.jdt.core.compiler.codegen.unusedLocal=preserve +org.eclipse.jdt.core.compiler.compliance=1.5 +org.eclipse.jdt.core.compiler.debug.lineNumber=generate +org.eclipse.jdt.core.compiler.debug.localVariable=generate +org.eclipse.jdt.core.compiler.debug.sourceFile=generate +org.eclipse.jdt.core.compiler.problem.assertIdentifier=error +org.eclipse.jdt.core.compiler.problem.enumIdentifier=error +org.eclipse.jdt.core.compiler.source=1.5 +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_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_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_selector_in_method_invocation=0 +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.blank_lines_after_imports=1 +org.eclipse.jdt.core.formatter.blank_lines_after_package=1 +org.eclipse.jdt.core.formatter.blank_lines_before_field=0 +org.eclipse.jdt.core.formatter.blank_lines_before_first_class_body_declaration=0 +org.eclipse.jdt.core.formatter.blank_lines_before_imports=1 +org.eclipse.jdt.core.formatter.blank_lines_before_member_type=1 +org.eclipse.jdt.core.formatter.blank_lines_before_method=1 +org.eclipse.jdt.core.formatter.blank_lines_before_new_chunk=1 +org.eclipse.jdt.core.formatter.blank_lines_before_package=0 +org.eclipse.jdt.core.formatter.blank_lines_between_import_groups=1 +org.eclipse.jdt.core.formatter.blank_lines_between_type_declarations=1 +org.eclipse.jdt.core.formatter.brace_position_for_annotation_type_declaration=end_of_line +org.eclipse.jdt.core.formatter.brace_position_for_anonymous_type_declaration=end_of_line +org.eclipse.jdt.core.formatter.brace_position_for_array_initializer=end_of_line +org.eclipse.jdt.core.formatter.brace_position_for_block=end_of_line +org.eclipse.jdt.core.formatter.brace_position_for_block_in_case=end_of_line +org.eclipse.jdt.core.formatter.brace_position_for_constructor_declaration=end_of_line +org.eclipse.jdt.core.formatter.brace_position_for_enum_constant=end_of_line +org.eclipse.jdt.core.formatter.brace_position_for_enum_declaration=end_of_line +org.eclipse.jdt.core.formatter.brace_position_for_method_declaration=end_of_line +org.eclipse.jdt.core.formatter.brace_position_for_switch=end_of_line +org.eclipse.jdt.core.formatter.brace_position_for_type_declaration=end_of_line +org.eclipse.jdt.core.formatter.comment.clear_blank_lines_in_block_comment=false +org.eclipse.jdt.core.formatter.comment.clear_blank_lines_in_javadoc_comment=false +org.eclipse.jdt.core.formatter.comment.format_block_comments=true +org.eclipse.jdt.core.formatter.comment.format_header=false +org.eclipse.jdt.core.formatter.comment.format_html=true +org.eclipse.jdt.core.formatter.comment.format_javadoc_comments=true +org.eclipse.jdt.core.formatter.comment.format_line_comments=true +org.eclipse.jdt.core.formatter.comment.format_source_code=true +org.eclipse.jdt.core.formatter.comment.indent_parameter_description=true +org.eclipse.jdt.core.formatter.comment.indent_root_tags=true +org.eclipse.jdt.core.formatter.comment.insert_new_line_before_root_tags=insert +org.eclipse.jdt.core.formatter.comment.insert_new_line_for_parameter=insert +org.eclipse.jdt.core.formatter.comment.line_length=80 +org.eclipse.jdt.core.formatter.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.format_guardian_clause_on_one_line=false +org.eclipse.jdt.core.formatter.indent_body_declarations_compare_to_annotation_declaration_header=true +org.eclipse.jdt.core.formatter.indent_body_declarations_compare_to_enum_constant_header=true +org.eclipse.jdt.core.formatter.indent_body_declarations_compare_to_enum_declaration_header=true +org.eclipse.jdt.core.formatter.indent_body_declarations_compare_to_type_header=true +org.eclipse.jdt.core.formatter.indent_breaks_compare_to_cases=true +org.eclipse.jdt.core.formatter.indent_empty_lines=false +org.eclipse.jdt.core.formatter.indent_statements_compare_to_block=true +org.eclipse.jdt.core.formatter.indent_statements_compare_to_body=true +org.eclipse.jdt.core.formatter.indent_switchstatements_compare_to_cases=true +org.eclipse.jdt.core.formatter.indent_switchstatements_compare_to_switch=false +org.eclipse.jdt.core.formatter.indentation.size=4 +org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_local_variable=insert +org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_member=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_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_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_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_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_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_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=4 +org.eclipse.jdt.core.formatter.use_tabs_only_for_leading_indentations=false +org.eclipse.jdt.core.formatter.wrap_before_binary_operator=true diff --git a/tests/.settings/org.eclipse.jdt.ui.prefs b/tests/.settings/org.eclipse.jdt.ui.prefs new file mode 100644 index 000000000..5278214b2 --- /dev/null +++ b/tests/.settings/org.eclipse.jdt.ui.prefs @@ -0,0 +1,5 @@ +#Sat Aug 22 23:48:16 PDT 2009 +cleanup_settings_version=2 +eclipse.preferences.version=1 +formatter_profile=_Astrid +formatter_settings_version=11 diff --git a/tests/Android.mk b/tests/Android.mk new file mode 100644 index 000000000..96dd1e2c0 --- /dev/null +++ b/tests/Android.mk @@ -0,0 +1,23 @@ +LOCAL_PATH:= $(call my-dir) +include $(CLEAR_VARS) + +# We only want this apk build for tests. +LOCAL_MODULE_TAGS := tests + +LOCAL_JAVA_LIBRARIES := android.test.runner + +# Include all test java files. +LOCAL_SRC_FILES := $(call all-java-files-under, src) + +# Notice that we don't have to include the src files of ApiDemos because, by +# running the tests using an instrumentation targeting ApiDemos, we +# automatically get all of its classes loaded into our environment. + +LOCAL_PACKAGE_NAME := AstridTests + +LOCAL_INSTRUMENTATION_FOR := Astrid + +LOCAL_SDK_VERSION := current + +include $(BUILD_PACKAGE) + diff --git a/tests/AndroidManifest.xml b/tests/AndroidManifest.xml new file mode 100644 index 000000000..24af24bfa --- /dev/null +++ b/tests/AndroidManifest.xml @@ -0,0 +1,25 @@ + + + + + + + + + + + + + + diff --git a/tests/build.properties b/tests/build.properties new file mode 100644 index 000000000..a6504854e --- /dev/null +++ b/tests/build.properties @@ -0,0 +1,15 @@ +# This file is used to override default values used by the Ant build system. +# +# This file must be checked in Version Control Systems, as it is +# integral to the build system of your project. + +# The name of your application package as defined in the manifest. +# Used by the 'uninstall' rule. +#application-package=com.example.myproject + +# The name of the source folder. +#source-folder=src + +# The name of the output folder. +#out-folder=bin + diff --git a/tests/build.xml b/tests/build.xml new file mode 100644 index 000000000..0002b4bbe --- /dev/null +++ b/tests/build.xml @@ -0,0 +1,61 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/tests/default.properties b/tests/default.properties new file mode 100644 index 000000000..19c96655d --- /dev/null +++ b/tests/default.properties @@ -0,0 +1,13 @@ +# 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 use, +# "build.properties", and override values to adapt the script to your +# project structure. + +# Indicates whether an apk should be generated for each density. +split.density=false +# Project target. +target=android-4 diff --git a/tests/plugin-src/com/todoroo/astrid/reminders/AlarmReceiverTests.java b/tests/plugin-src/com/todoroo/astrid/reminders/AlarmReceiverTests.java new file mode 100644 index 000000000..2d8996378 --- /dev/null +++ b/tests/plugin-src/com/todoroo/astrid/reminders/AlarmReceiverTests.java @@ -0,0 +1,130 @@ +package com.todoroo.astrid.reminders; + +import android.app.Notification; +import android.app.PendingIntent; +import android.content.Intent; + +import com.todoroo.andlib.test.utility.DateUtilities; +import com.todoroo.astrid.model.Task; + +public class AlarmReceiverTests extends PluginTestCase { + + /** simple test of task at deadline */ + public void testDeadlineReminder() { + Task task = new Task(); + task.setValue(Task.TITLE, "poop"); + task.setValue(Task.DUE_DATE, DateUtilities.now()); + + long id = createTask(task, null); + Intent alarmIntent = new Intent(); + alarmIntent.putExtra(AlarmReceiver.TOKEN_TASK_ID, id); + alarmIntent.putExtra(AlarmReceiver.TOKEN_IS_DEADLINE, true); + alarmIntent.putExtra(AlarmReceiver.TOKEN_IS_ALARMCLOCK, false); + + AlarmReceiver rx = new AlarmReceiver(); + AssertiveNotificationManager nm = new AssertiveNotificationManager(getContext()); + AlarmReceiver.notificationManager = nm; + rx.onReceive(getContext(), alarmIntent); + nm.assertNotified(); + } + + /** task at deadline, except hidden. no notification should sound */ + public void testDeadlineReminderExceptHidden() { + Task task = new Task(); + task.setValue(Task.TITLE, "poop"); + task.setValue(Task.DUE_DATE, DateUtilities.now()); + task.setValue(Task.HIDDEN_UNTIL, DateUtilities.now() + 100); + + long id = createTask(task, null); + Intent alarmIntent = new Intent(); + alarmIntent.putExtra(AlarmReceiver.TOKEN_TASK_ID, id); + alarmIntent.putExtra(AlarmReceiver.TOKEN_IS_DEADLINE, true); + alarmIntent.putExtra(AlarmReceiver.TOKEN_IS_ALARMCLOCK, false); + + AlarmReceiver rx = new AlarmReceiver(); + AssertiveNotificationManager nm = new AssertiveNotificationManager(getContext()); + AlarmReceiver.notificationManager = nm; + rx.onReceive(getContext(), alarmIntent); + nm.assertNotNotified(); + } + + /** task upcoming */ + public void testUpcomingReminder() { + Task task = new Task(); + task.setValue(Task.TITLE, "poop"); + task.setValue(Task.DUE_DATE, DateUtilities.now() + 100); + + long id = createTask(task, null); + Intent alarmIntent = new Intent(); + alarmIntent.putExtra(AlarmReceiver.TOKEN_TASK_ID, id); + alarmIntent.putExtra(AlarmReceiver.TOKEN_IS_DEADLINE, false); + alarmIntent.putExtra(AlarmReceiver.TOKEN_IS_ALARMCLOCK, false); + + AlarmReceiver rx = new AlarmReceiver(); + AssertiveNotificationManager nm = new AssertiveNotificationManager(getContext()); + AlarmReceiver.notificationManager = nm; + rx.onReceive(getContext(), alarmIntent); + nm.assertNotified(); + } + + /** task overdue */ + public void testOverdueReminder() { + Task task = new Task(); + task.setValue(Task.TITLE, "poop"); + task.setValue(Task.DUE_DATE, DateUtilities.now() - 100); + + long id = createTask(task, null); + Intent alarmIntent = new Intent(); + alarmIntent.putExtra(AlarmReceiver.TOKEN_TASK_ID, id); + alarmIntent.putExtra(AlarmReceiver.TOKEN_IS_DEADLINE, false); + alarmIntent.putExtra(AlarmReceiver.TOKEN_IS_ALARMCLOCK, false); + + AlarmReceiver rx = new AlarmReceiver(); + AssertiveNotificationManager nm = new AssertiveNotificationManager(getContext()); + AlarmReceiver.notificationManager = nm; + rx.onReceive(getContext(), alarmIntent); + nm.assertNotified(); + } + + /** task alarm clock */ + public void testAlarmClock() { + Task task = new Task(); + task.setValue(Task.TITLE, "poop"); + task.setValue(Task.DUE_DATE, DateUtilities.now()); + + long id = createTask(task, null); + Intent alarmIntent = new Intent(); + alarmIntent.putExtra(AlarmReceiver.TOKEN_TASK_ID, id); + alarmIntent.putExtra(AlarmReceiver.TOKEN_IS_DEADLINE, true); + alarmIntent.putExtra(AlarmReceiver.TOKEN_IS_ALARMCLOCK, true); + + AlarmReceiver rx = new AlarmReceiver(); + AssertiveNotificationManager nm = new AssertiveNotificationManager(getContext()); + AlarmReceiver.notificationManager = nm; + rx.onReceive(getContext(), alarmIntent); + nm.assertNotified(); + assertTrue((nm.getNotification().flags & Notification.FLAG_INSISTENT) > 0); + } + + /** test the intent that the alarm receiver creates */ + public void testOpenIntent() throws Exception { + Task task = new Task(); + task.setValue(Task.TITLE, "poop"); + task.setValue(Task.DUE_DATE, DateUtilities.now()); + + long id = createTask(task, null); + Intent alarmIntent = new Intent(); + alarmIntent.putExtra(AlarmReceiver.TOKEN_TASK_ID, id); + alarmIntent.putExtra(AlarmReceiver.TOKEN_IS_DEADLINE, true); + alarmIntent.putExtra(AlarmReceiver.TOKEN_IS_ALARMCLOCK, false); + + AlarmReceiver rx = new AlarmReceiver(); + AssertiveNotificationManager nm = new AssertiveNotificationManager(getContext()); + AlarmReceiver.notificationManager = nm; + rx.onReceive(getContext(), alarmIntent); + nm.assertNotified(); + + PendingIntent intent = nm.getNotification().contentIntent; + intent.send(); + } +} diff --git a/tests/plugin-src/com/todoroo/astrid/reminders/AssertiveNotificationManager.java b/tests/plugin-src/com/todoroo/astrid/reminders/AssertiveNotificationManager.java new file mode 100644 index 000000000..ec7e47d44 --- /dev/null +++ b/tests/plugin-src/com/todoroo/astrid/reminders/AssertiveNotificationManager.java @@ -0,0 +1,47 @@ +package com.todoroo.astrid.reminders; + +import android.app.Notification; +import android.content.Context; + +import com.todoroo.andlib.service.NotificationManager.AndroidNotificationManager; + +/** + * Notification manager that provides notifications and adds an + * extra method for verification. + * + * @author timsu + * + */ +public class AssertiveNotificationManager extends AndroidNotificationManager { + + Notification notification = null; + + public AssertiveNotificationManager(Context context) { + super(context); + + } + + @Override + public void notify(int id, Notification notification) { + super.notify(id, notification); + this.notification = notification; + } + + public void assertNotified() { + if(notification == null) + throw new AssertionError("Notification was not triggered"); + } + + public void assertNotNotified() { + if(notification != null) + throw new AssertionError("Notification was triggered"); + } + + public Notification getNotification() { + return notification; + } + + public void clear() { + notification = null; + } +} \ No newline at end of file diff --git a/tests/plugin-src/com/todoroo/astrid/reminders/NotificationServiceTests.java b/tests/plugin-src/com/todoroo/astrid/reminders/NotificationServiceTests.java new file mode 100644 index 000000000..9a55a5b3f --- /dev/null +++ b/tests/plugin-src/com/todoroo/astrid/reminders/NotificationServiceTests.java @@ -0,0 +1,110 @@ +package com.todoroo.astrid.reminders; + +import java.util.Date; + +import android.content.Context; +import android.content.SharedPreferences; +import android.content.SharedPreferences.Editor; +import android.content.res.Resources; +import android.preference.PreferenceManager; +import android.test.AndroidTestCase; + +import com.todoroo.astrid.R; +import com.todoroo.astrid.reminders.service.NotificationService; + +public class NotificationServiceTests extends AndroidTestCase { + + /** + * Test quiet hour determination logic + */ + public void testQuietHoursWrapped() { + Context context = getContext(); + + // test wrapped quiet hours + SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(getContext()); + Editor editor = prefs.edit(); + Resources r = getContext().getResources(); + + editor.putString(r.getString(R.string.rmd_EPr_quiet_hours_start_key), + Integer.toString(22)); + editor.putString(r.getString(R.string.rmd_EPr_quiet_hours_end_key), + Integer.toString(8)); + editor.commit(); + + Date date = new Date(); + date.setHours(21); + date.setMinutes(59); + assertFalse(NotificationService.isInQuietHours(context, date)); + + date.setHours(22); + date.setMinutes(0); + assertTrue(NotificationService.isInQuietHours(context, date)); + + date.setHours(23); + assertTrue(NotificationService.isInQuietHours(context, date)); + + date.setHours(0); + assertTrue(NotificationService.isInQuietHours(context, date)); + + date.setHours(7); + date.setMinutes(59); + assertTrue(NotificationService.isInQuietHours(context, date)); + + date.setHours(8); + date.setMinutes(0); + assertFalse(NotificationService.isInQuietHours(context, date)); + + date.setHours(12); + assertFalse(NotificationService.isInQuietHours(context, date)); + + date.setHours(20); + assertFalse(NotificationService.isInQuietHours(context, date)); + } + + /** + * Test quiet hour determination logic + */ + public void testQuietHoursUnwrapped() { + Context context = getContext(); + + // test wrapped quiet hours + SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(getContext()); + Editor editor = prefs.edit(); + Resources r = getContext().getResources(); + + editor.putString(r.getString(R.string.rmd_EPr_quiet_hours_start_key), + Integer.toString(10)); + editor.putString(r.getString(R.string.rmd_EPr_quiet_hours_end_key), + Integer.toString(16)); + editor.commit(); + + Date date = new Date(); + date.setHours(9); + date.setMinutes(59); + assertFalse(NotificationService.isInQuietHours(context, date)); + + date.setHours(10); + date.setMinutes(0); + assertTrue(NotificationService.isInQuietHours(context, date)); + + date.setHours(11); + assertTrue(NotificationService.isInQuietHours(context, date)); + + date.setHours(13); + assertTrue(NotificationService.isInQuietHours(context, date)); + + date.setHours(15); + date.setMinutes(59); + assertTrue(NotificationService.isInQuietHours(context, date)); + + date.setHours(16); + date.setMinutes(0); + assertFalse(NotificationService.isInQuietHours(context, date)); + + date.setHours(23); + assertFalse(NotificationService.isInQuietHours(context, date)); + + date.setHours(0); + assertFalse(NotificationService.isInQuietHours(context, date)); + } +} diff --git a/tests/plugin-src/com/todoroo/astrid/reminders/PluginTestCase.java b/tests/plugin-src/com/todoroo/astrid/reminders/PluginTestCase.java new file mode 100644 index 000000000..456b3a966 --- /dev/null +++ b/tests/plugin-src/com/todoroo/astrid/reminders/PluginTestCase.java @@ -0,0 +1,43 @@ +package com.todoroo.astrid.reminders; + +import java.util.HashMap; +import java.util.Map.Entry; + +import com.todoroo.andlib.service.Autowired; +import com.todoroo.astrid.dao.MetadataDao; +import com.todoroo.astrid.dao.TaskDao; +import com.todoroo.astrid.model.Metadata; +import com.todoroo.astrid.model.Task; +import com.todoroo.astrid.test.DatabaseTestCase; + +public class PluginTestCase extends DatabaseTestCase { + + @Autowired + public TaskDao taskDao; + + @Autowired + public MetadataDao metadataDao; + + /** + * Helper method to create a task. + * @param task + * @param metadata + * @return task id + */ + public long createTask(Task task, HashMap metadata) { + taskDao.save(database, task, false); + + if(metadata != null) { + Metadata metadataItem = new Metadata(); + metadataItem.setValue(Metadata.TASK, task.getId()); + for(Entry entry : metadata.entrySet()) { + metadataItem.setValue(Metadata.KEY, entry.getKey()); + metadataItem.setValue(Metadata.VALUE, entry.getValue()); + metadataDao.save(database, metadataItem); + } + } + + return task.getId(); + } + +} diff --git a/tests/plugin-src/com/todoroo/astrid/reminders/service/ReminderServiceTests.java b/tests/plugin-src/com/todoroo/astrid/reminders/service/ReminderServiceTests.java new file mode 100644 index 000000000..410a490fa --- /dev/null +++ b/tests/plugin-src/com/todoroo/astrid/reminders/service/ReminderServiceTests.java @@ -0,0 +1,282 @@ +package com.todoroo.astrid.reminders.service; + +import java.util.HashMap; + +import android.database.sqlite.SQLiteQueryBuilder; + +import com.todoroo.andlib.test.data.TodorooCursor; +import com.todoroo.andlib.test.utility.DateUtilities; +import com.todoroo.astrid.model.Task; +import com.todoroo.astrid.reminders.AlarmReceiver; +import com.todoroo.astrid.reminders.Constants; +import com.todoroo.astrid.reminders.PluginTestCase; +import com.todoroo.astrid.reminders.service.ReminderService.Alarm; + +public class ReminderServiceTests extends PluginTestCase { + + /** + * Test task fetching mechanism for reminders + */ + @SuppressWarnings("unused") + public void testTaskReminderFetch() { + ReminderService service = new ReminderService(getContext()); + + SQLiteQueryBuilder baseQuery = service.createBaseQuery(); + TodorooCursor cursor = service.fetchTasksWithReminders(baseQuery); + assertEquals(0, cursor.getCount()); + cursor.close(); + + // add new tasks + int now = DateUtilities.now(); + Task task = new Task(); + HashMap metadata = new HashMap(); + + task.setValue(Task.TITLE, "normal"); + task.setValue(Task.DUE_DATE, now + 10); + metadata.put(Constants.KEY_REMINDER, Integer.toString(Constants.REMINDER_AT_DEADLINE)); + long normalId = createTask(task, metadata); + + task.clear(); + task.setValue(Task.TITLE, "completed"); + task.setValue(Task.DUE_DATE, now + 10); + task.setValue(Task.COMPLETION_DATE, now + 10); + metadata.put(Constants.KEY_REMINDER, Integer.toString(Constants.REMINDER_ALARM_CLOCK)); + long completedId = createTask(task, metadata); + + task.clear(); + task.setValue(Task.TITLE, "hidden"); + task.setValue(Task.DUE_DATE, now + 10); + task.setValue(Task.HIDDEN_UNTIL, now + 10); + metadata.put(Constants.KEY_REMINDER, Integer.toString(Constants.REMINDER_INCREASING)); + long hiddenId = createTask(task, metadata); + + task.clear(); + task.setValue(Task.TITLE, "noalarm"); + task.setValue(Task.DUE_DATE, now + 10); + metadata.put(Constants.KEY_REMINDER, Integer.toString(Constants.REMINDER_NONE)); + long noAlarmId = createTask(task, metadata); + + task.clear(); + task.setValue(Task.TITLE, "overdue"); + task.setValue(Task.DUE_DATE, now - 10); + metadata.put(Constants.KEY_REMINDER, Integer.toString(Constants.REMINDER_AT_DEADLINE)); + long overdueId = createTask(task, metadata); + + cursor = service.fetchTasksWithReminders(baseQuery); + assertEquals(2, cursor.getCount()); + cursor.moveToFirst(); + task.readFromCursor(cursor, ReminderService.PROPERTIES); + assertEquals(task.getId(), normalId); + assertEquals(task.getValue(ReminderService.REMINDER_MODE), + Integer.toString(Constants.REMINDER_AT_DEADLINE)); + cursor.moveToNext(); + task.readFromCursor(cursor, ReminderService.PROPERTIES); + assertEquals(task.getId(), hiddenId); + assertEquals(task.getValue(ReminderService.REMINDER_MODE), + Integer.toString(Constants.REMINDER_INCREASING)); + cursor.close(); + } + + /** + * Test task fetching mechanism for overdue interval + */ + @SuppressWarnings("unused") + public void testTaskOverdueFetch() { + ReminderService service = new ReminderService(getContext()); + + SQLiteQueryBuilder baseQuery = service.createBaseQuery(); + TodorooCursor cursor = service.fetchTasksWithOverdueReminders(baseQuery); + assertEquals(0, cursor.getCount()); + cursor.close(); + + // add new tasks + int now = DateUtilities.now(); + Task task = new Task(); + HashMap metadata = new HashMap(); + + task.setValue(Task.TITLE, "normal"); + task.setValue(Task.DUE_DATE, now - 10); + metadata.put(Constants.KEY_OVERDUE, Integer.toString(1500)); + long normalId = createTask(task, metadata); + + task.clear(); + task.setValue(Task.TITLE, "completed"); + task.setValue(Task.DUE_DATE, now - 10); + task.setValue(Task.COMPLETION_DATE, now - 10); + metadata.put(Constants.KEY_OVERDUE, Integer.toString(1500)); + long completedId = createTask(task, metadata); + + task.clear(); + task.setValue(Task.TITLE, "hidden"); + task.setValue(Task.DUE_DATE, now - 10); + task.setValue(Task.HIDDEN_UNTIL, now + 10); + metadata.put(Constants.KEY_OVERDUE, Integer.toString(1500)); + long hiddenId = createTask(task, metadata); + + task.clear(); + task.setValue(Task.TITLE, "noalarm"); + task.setValue(Task.DUE_DATE, now - 10); + metadata.put(Constants.KEY_OVERDUE, Integer.toString(0)); + long noAlarmId = createTask(task, metadata); + + task.clear(); + task.setValue(Task.TITLE, "not due"); + task.setValue(Task.DUE_DATE, now + 10); + metadata.put(Constants.KEY_OVERDUE, Integer.toString(0)); + long notDueId = createTask(task, metadata); + + cursor = service.fetchTasksWithOverdueReminders(baseQuery); + assertEquals(2, cursor.getCount()); + cursor.moveToFirst(); + task.readFromCursor(cursor, ReminderService.PROPERTIES); + assertEquals(task.getId(), normalId); + assertEquals(task.getValue(ReminderService.OVERDUE_INTERVAL), + Integer.valueOf(1500)); + cursor.moveToNext(); + task.readFromCursor(cursor, ReminderService.PROPERTIES); + assertEquals(task.getId(), hiddenId); + assertEquals(task.getValue(ReminderService.OVERDUE_INTERVAL), + Integer.valueOf(1500)); + cursor.close(); + } + + /** + * Test task fetching mechanism for reminders + */ + public void testTaskFetchForReminders() { + ReminderService service = new ReminderService(getContext()); + + // try invalid + assertNull("could fetch invalid", service.fetchTask(-99)); + + // add new tasks + Task task = new Task(); + HashMap metadata = new HashMap(); + task.setValue(Task.TITLE, "salami"); + task.setValue(Task.DUE_DATE, 100); + metadata.put(Constants.KEY_OVERDUE, "123"); + metadata.put(Constants.KEY_LAST_NOTIFIED, "345"); + metadata.put(Constants.KEY_REMINDER, "567"); + long id = createTask(task, metadata); + + task = service.fetchTask(id); + assertEquals(task.getValue(Task.TITLE), "salami"); + assertEquals(task.getValue(ReminderService.OVERDUE_INTERVAL), Integer.valueOf(123)); + assertEquals(task.getValue(ReminderService.LAST_NOTIFIED), Integer.valueOf(345)); + assertEquals(task.getValue(ReminderService.REMINDER_MODE), Integer.valueOf(567)); + + task.setValue(ReminderService.OVERDUE_INTERVAL, 321); + task.setValue(ReminderService.LAST_NOTIFIED, 543); + task.setValue(ReminderService.REMINDER_MODE, 765); + + service.writeReminderOptions(task); + task = service.fetchTask(id); + + assertEquals(task.getValue(ReminderService.OVERDUE_INTERVAL), Integer.valueOf(321)); + assertEquals(task.getValue(ReminderService.LAST_NOTIFIED), Integer.valueOf(543)); + assertEquals(task.getValue(ReminderService.REMINDER_MODE), Integer.valueOf(765)); + } + + /** + * Test alarm generation + */ + public void testAlarmGeneration() { + ReminderService service = new ReminderService(getContext()); + int now = DateUtilities.now(); + + // test simple task with deadline + Task task = new Task(); + task.setValue(Task.TITLE, "blah"); + task.setValue(Task.DUE_DATE, now + 10000); + task.setValue(ReminderService.REMINDER_MODE, Constants.REMINDER_AT_DEADLINE); + Alarm alarm = service.createReminderAlarm(task); + assertEquals(task.getValue(Task.DUE_DATE), Integer.valueOf(alarm.alarmTime)); + + // test simple task no reminders + task.setValue(ReminderService.REMINDER_MODE, Constants.REMINDER_NONE); + alarm = service.createReminderAlarm(task); + assertNull(alarm); + + // test simple task with deadline passed and no overdue interval + task.setValue(Task.DUE_DATE, now - 10); + task.setValue(ReminderService.REMINDER_MODE, Constants.REMINDER_AT_DEADLINE); + alarm = service.createReminderAlarm(task); + assertNull(alarm); + + // test alarm clock task + task.setValue(Task.DUE_DATE, now + 10000); + task.setValue(ReminderService.REMINDER_MODE, Constants.REMINDER_ALARM_CLOCK); + alarm = service.createReminderAlarm(task); + assertEquals(task.getValue(Task.DUE_DATE), Integer.valueOf(alarm.alarmTime)); + + // test overdue alarm clock task + task.setValue(Task.DUE_DATE, now - 10); + alarm = service.createReminderAlarm(task); + assertNull(alarm); + + // test increasing 30 days from now + task.setValue(ReminderService.REMINDER_MODE, Constants.REMINDER_INCREASING); + task.setValue(ReminderService.LAST_NOTIFIED, now); + task.setValue(Task.DUE_DATE, now + 30 * 86400); + alarm = service.createReminderAlarm(task); + assertTrue(alarm.alarmTime > now + 5 * 86400 && + alarm.alarmTime < now + 25 * 86400); + + // test increasing 30 days from now, with last notified of 100 days ago + task.setValue(ReminderService.REMINDER_MODE, Constants.REMINDER_INCREASING); + task.setValue(Task.DUE_DATE, now + 30 * 86400); + task.setValue(ReminderService.LAST_NOTIFIED, now - 100 * 86400); + alarm = service.createReminderAlarm(task); + assertTrue(alarm.alarmTime > 0 && + alarm.alarmTime < now + 1 * 86400); + + // test increasing when we're within the day boundary and task is + // not defined with a specific time + task.setValue(Task.URGENCY, Task.URGENCY_THIS_WEEK); + task.setValue(Task.DUE_DATE, now + 80000); + task.setValue(ReminderService.LAST_NOTIFIED, now); + alarm = service.createReminderAlarm(task); + assertTrue(alarm.intent.getBooleanExtra(AlarmReceiver.TOKEN_IS_DEADLINE, false)); + assertEquals(task.getValue(Task.DUE_DATE), Integer.valueOf(alarm.alarmTime)); + + // now same due date, but we've defined specific time + task.setValue(Task.URGENCY, Task.URGENCY_SPECIFIC_DAY_TIME); + alarm = service.createReminderAlarm(task); + assertTrue(DateUtilities.now() < alarm.alarmTime && + alarm.alarmTime < task.getValue(Task.DUE_DATE)); + + // move the due date right up against actual + task.setValue(Task.DUE_DATE, now + 1000); + alarm = service.createReminderAlarm(task); + assertEquals(task.getValue(Task.DUE_DATE), Integer.valueOf(alarm.alarmTime)); + assertTrue(alarm.intent.getBooleanExtra(AlarmReceiver.TOKEN_IS_DEADLINE, false)); + + // and after due date + task.setValue(Task.DUE_DATE, now - 80000); + alarm = service.createReminderAlarm(task); + assertNull(alarm); + } + + /** + * Test alarm generation (overdue) + */ + public void testOverdueAlarmGeneration() { + ReminderService service = new ReminderService(getContext()); + int now = DateUtilities.now(); + + // test simple overdue + Task task = new Task(); + task.setValue(Task.TITLE, "blah"); + + task.setValue(Task.DUE_DATE, now - 100); + task.setValue(ReminderService.OVERDUE_INTERVAL, 1000); + Alarm alarm = service.createOverdueAlarm(task); + assertTrue(now + 500 < alarm.alarmTime && alarm.alarmTime < now + 1500); + + // same case, but almost exact due as normal + task.setValue(Task.DUE_DATE, DateUtilities.now()); + alarm = service.createOverdueAlarm(task); + assertTrue(now + 500 < alarm.alarmTime && alarm.alarmTime < now + 1500); + } + +} diff --git a/tests/plugin-src/com/todoroo/astrid/rmilk/RTMTestCase.java b/tests/plugin-src/com/todoroo/astrid/rmilk/RTMTestCase.java new file mode 100644 index 000000000..abaea3b9f --- /dev/null +++ b/tests/plugin-src/com/todoroo/astrid/rmilk/RTMTestCase.java @@ -0,0 +1,5 @@ +package com.todoroo.astrid.rmilk; + +public class RTMTestCase { + String token = "cb4281f7154114ed0e4ccc3a02d373c5a2429532"; +} diff --git a/tests/res/.do.not.delete b/tests/res/.do.not.delete new file mode 100644 index 000000000..e69de29bb diff --git a/tests/src/com/todoroo/andlib/service/DependencyInjectionTests.java b/tests/src/com/todoroo/andlib/service/DependencyInjectionTests.java new file mode 100644 index 000000000..a4ee76662 --- /dev/null +++ b/tests/src/com/todoroo/andlib/service/DependencyInjectionTests.java @@ -0,0 +1,240 @@ +package com.todoroo.andlib.service; + +import java.lang.reflect.Field; + +import android.test.AndroidTestCase; + +import com.todoroo.andlib.service.AbstractDependencyInjector; +import com.todoroo.andlib.service.Autowired; +import com.todoroo.andlib.service.DependencyInjectionService; + +public class DependencyInjectionTests extends AndroidTestCase { + + public void testNoAutowire() { + DependencyInjectionService service = new DependencyInjectionService(); + service.setInjectors(new AbstractDependencyInjector[] {}); + + Object test = new Object(); + service.inject(test); + } + + public void testSimpleStringInjectionAutowire() { + DependencyInjectionService service = new DependencyInjectionService(); + service.setInjectors(new AbstractDependencyInjector[] { + new AbstractDependencyInjector() { + + public Object getInjection(Object object, Field field) { + if(field.getName().equals("foo")) + return "bar"; + return null; + } + } + }); + + // test various permissions + Object test = new Object() { + @Autowired + public String foo; + + @Override + public String toString() { + return foo; + } + }; + service.inject(test); + assertEquals("bar", test.toString()); + + test = new Object() { + @Autowired + String foo; + + @Override + public String toString() { + return foo; + } + }; + service.inject(test); + assertEquals("bar", test.toString()); + + test = new Object() { + @Autowired + protected String foo; + + @Override + public String toString() { + return foo; + } + }; + service.inject(test); + assertEquals("bar", test.toString()); + + test = new Object() { + @Autowired + private String foo; + + @Override + public String toString() { + return foo; + } + }; + service.inject(test); + assertEquals("bar", test.toString()); + + // test no annotation + test = new Object() { + public String foo; + + @Override + public String toString() { + return foo; + } + }; + service.inject(test); + assertNull( test.toString()); + } + + public void testHierarchicalStringInjectionAutowire() { + DependencyInjectionService service = new DependencyInjectionService(); + service.setInjectors(new AbstractDependencyInjector[] { + new AbstractDependencyInjector() { + + public Object getInjection(Object object, Field field) { + if(field.getName().equals("foo")) + return "bar"; + return null; + } + }, + new AbstractDependencyInjector() { + + public Object getInjection(Object object, Field field) { + return "malarkey"; + } + } + }); + + Object test = new Object() { + @Autowired + public String foo; + + @Override + public String toString() { + return foo; + } + }; + service.inject(test); + assertEquals("bar", test.toString()); + + test = new Object() { + @Autowired + public String forks; + + @Override + public String toString() { + return forks; + } + }; + service.inject(test); + assertEquals("malarkey", test.toString()); + } + + public void testMissingInjection() { + DependencyInjectionService service = new DependencyInjectionService(); + service.setInjectors(new AbstractDependencyInjector[] { + new AbstractDependencyInjector() { + + public Object getInjection(Object object, Field field) { + if(field.getName().equals("wozzle")) + return "bar"; + return null; + } + } + }); + + Object test = new Object() { + @Autowired + public String foo; + + @Override + public String toString() { + return foo; + } + }; + + try { + service.inject(test); + fail("could inject with missing injector"); + } catch (RuntimeException e) { + // expected + } + + assertNull(test.toString()); + } + + public void testMultipleInjection() { + DependencyInjectionService service = new DependencyInjectionService(); + service.setInjectors(new AbstractDependencyInjector[] { + new AbstractDependencyInjector() { + + public Object getInjection(Object object, Field field) { + if(field.getName().equals("foo")) + return "bar"; + return null; + } + } + }); + + Object test1 = new Object() { + @Autowired + public String foo; + + @Override + public String toString() { + return foo; + } + }; + Object test2 = new Object() { + @Autowired + public String foo; + + @Override + public String toString() { + return foo; + } + }; + service.inject(test1); + service.inject(test2); + assertEquals("bar", test1.toString()); + assertEquals("bar", test2.toString()); + } + + public static class ParentInjectee { + @Autowired + protected String foo; + } + + public static class ChildInjectee extends ParentInjectee { + @Autowired + protected String bar; + } + + public void testInheritedInjection() { + DependencyInjectionService service = new DependencyInjectionService(); + service.setInjectors(new AbstractDependencyInjector[] { + new AbstractDependencyInjector() { + + public Object getInjection(Object object, Field field) { + if(field.getName().equals("foo")) + return "gotfoo"; + else if(field.getName().equals("bar")) + return "hasbar"; + return null; + } + } + }); + + ChildInjectee child = new ChildInjectee(); + service.inject(child); + assertEquals("gotfoo", child.foo); + assertEquals("hasbar", child.bar); + } +} diff --git a/tests/src/com/todoroo/andlib/service/TestDependencyInjector.java b/tests/src/com/todoroo/andlib/service/TestDependencyInjector.java new file mode 100644 index 000000000..e1edf70ec --- /dev/null +++ b/tests/src/com/todoroo/andlib/service/TestDependencyInjector.java @@ -0,0 +1,59 @@ +package com.todoroo.andlib.service; + +import java.lang.reflect.Field; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashMap; +import java.util.Iterator; + +public class TestDependencyInjector implements AbstractDependencyInjector { + + /** + * Dependencies this class knows how to handle + */ + private final HashMap injectables = new HashMap(); + + private String name; + + public TestDependencyInjector(String name) { + this.name = name; + } + + public void addInjectable(String field, Object injection) { + injectables.put(field, injection); + } + + public Object getInjection(Object object, Field field) { + if(injectables.containsKey(field.getName())) { + return injectables.get(field.getName()); + } + + return null; + } + + // --- static stuff + + /** + * Install TestDependencyInjector above other injectors + */ + public synchronized static TestDependencyInjector initialize(String name) { + ArrayList list = + new ArrayList(Arrays.asList(DependencyInjectionService.getInstance().getInjectors())); + for(Iterator i = list.iterator(); i.hasNext(); ) { + AbstractDependencyInjector injector = i.next(); + + // if another one of these injectors already exists in the + // stack, remove it + if(injector instanceof TestDependencyInjector) { + if(((TestDependencyInjector)injector).name.equals(name)) + i.remove(); + } + } + + TestDependencyInjector instance = new TestDependencyInjector(name); + list.add(0, instance); + DependencyInjectionService.getInstance().setInjectors(list.toArray(new AbstractDependencyInjector[list.size()])); + return instance; + } + +} diff --git a/tests/src/com/todoroo/andlib/test/SimpleAndroidTest.java b/tests/src/com/todoroo/andlib/test/SimpleAndroidTest.java new file mode 100644 index 000000000..35d4b7ec5 --- /dev/null +++ b/tests/src/com/todoroo/andlib/test/SimpleAndroidTest.java @@ -0,0 +1,11 @@ +package com.todoroo.andlib.test; + +import junit.framework.Assert; +import android.test.AndroidTestCase; + +public class SimpleAndroidTest extends AndroidTestCase { + + public void testSimpleAssert() throws Throwable { + Assert.assertTrue(true); + } +} diff --git a/tests/src/com/todoroo/andlib/test/TestUtilities.java b/tests/src/com/todoroo/andlib/test/TestUtilities.java new file mode 100644 index 000000000..edd2dc608 --- /dev/null +++ b/tests/src/com/todoroo/andlib/test/TestUtilities.java @@ -0,0 +1,25 @@ +package com.todoroo.andlib.test; + + +/** + * Utility methods used in unit tests + * + * @author Tim Su + * + */ +public class TestUtilities { + + /** + * Sleep, suppressing exceptions + * + * @param millis + */ + public static void sleepDeep(long millis) { + try { + Thread.sleep(millis); + } catch (InterruptedException e) { + // do nothing + } + } + +} diff --git a/tests/src/com/todoroo/andlib/test/TodorooTestCase.java b/tests/src/com/todoroo/andlib/test/TodorooTestCase.java new file mode 100644 index 000000000..fc132d43a --- /dev/null +++ b/tests/src/com/todoroo/andlib/test/TodorooTestCase.java @@ -0,0 +1,26 @@ +package com.todoroo.andlib.test; + +import android.test.AndroidTestCase; + +import com.todoroo.andlib.service.ContextManager; +import com.todoroo.andlib.service.DependencyInjectionService; + +/** + * Base test case for Astrid tests + * + * @author Tim Su + * + */ +public class TodorooTestCase extends AndroidTestCase { + + public TodorooTestCase() { + DependencyInjectionService.getInstance().inject(this); + } + + @Override + protected void setUp() throws Exception { + super.setUp(); + ContextManager.setContext(this.getContext()); + } + +} diff --git a/tests/src/com/todoroo/astrid/ActivityTests.java b/tests/src/com/todoroo/astrid/ActivityTests.java new file mode 100644 index 000000000..24ee77fb5 --- /dev/null +++ b/tests/src/com/todoroo/astrid/ActivityTests.java @@ -0,0 +1,34 @@ +/* + * Copyright (C) 2008 The Android Open Source Project + * + * 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.todoroo.astrid; + +import junit.framework.Test; +import junit.framework.TestSuite; + +import android.test.suitebuilder.TestSuiteBuilder; + +/** + * A test suite containing activity-related tests + */ +public class ActivityTests extends TestSuite { + + public static Test suite() { + return new TestSuiteBuilder(ActivityTests.class) + .includePackages("com.todoroo.astrid.activity") + .build(); + } +} diff --git a/tests/src/com/todoroo/astrid/AllTests.java b/tests/src/com/todoroo/astrid/AllTests.java new file mode 100644 index 000000000..2f1b2e1be --- /dev/null +++ b/tests/src/com/todoroo/astrid/AllTests.java @@ -0,0 +1,53 @@ +/* + * Copyright (C) 2008 The Android Open Source Project + * + * 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.todoroo.astrid; + +import junit.framework.Test; +import junit.framework.TestSuite; + +import android.test.suitebuilder.TestSuiteBuilder; + +/** + * A test suite containing all tests for ApiDemos. + * + * To run all suites found in this apk: + * $ adb shell am instrument -w \ + * com.example.android.apis.tests/android.test.InstrumentationTestRunner + * + * To run just this suite from the command line: + * $ adb shell am instrument -w \ + * -e class com.example.android.apis.AllTests \ + * com.example.android.apis.tests/android.test.InstrumentationTestRunner + * + * To run an individual test case, e.g. {@link com.example.android.apis.os.MorseCodeConverterTest}: + * $ adb shell am instrument -w \ + * -e class com.example.android.apis.os.MorseCodeConverterTest \ + * com.example.android.apis.tests/android.test.InstrumentationTestRunner + * + * To run an individual test, e.g. {@link com.example.android.apis.os.MorseCodeConverterTest#testCharacterS()}: + * $ adb shell am instrument -w \ + * -e class com.example.android.apis.os.MorseCodeConverterTest#testCharacterS \ + * com.example.android.apis.tests/android.test.InstrumentationTestRunner + */ +public class AllTests extends TestSuite { + + public static Test suite() { + return new TestSuiteBuilder(AllTests.class) + .includeAllPackagesUnderHere() + .build(); + } +} diff --git a/tests/src/com/todoroo/astrid/dao/BasicDatabaseTests.java b/tests/src/com/todoroo/astrid/dao/BasicDatabaseTests.java new file mode 100644 index 000000000..9beeaf5a1 --- /dev/null +++ b/tests/src/com/todoroo/astrid/dao/BasicDatabaseTests.java @@ -0,0 +1,30 @@ +package com.todoroo.astrid.dao; + +import com.todoroo.astrid.test.DatabaseTestCase; + +import android.database.sqlite.SQLiteDatabase; + + +public class BasicDatabaseTests extends DatabaseTestCase { + + /** + * Test that it's possible to open the database multiple times, to no effect + */ + public void testOpenMultipleTimes() { + SQLiteDatabase sqlDatabase = database.getDatabase(); + database.openForReading(getContext()); + assertEquals(sqlDatabase, database.getDatabase()); + database.openForWriting(getContext()); + assertEquals(sqlDatabase, database.getDatabase()); + database.openForReading(getContext()); + assertEquals(sqlDatabase, database.getDatabase()); + } + + + public void testCloseAndReopen() { + SQLiteDatabase sqlDatabase = database.getDatabase(); + database.close(); + database.openForReading(getContext()); + assertNotSame(sqlDatabase, database.getDatabase()); + } +} diff --git a/tests/src/com/todoroo/astrid/dao/MetadataDaoTests.java b/tests/src/com/todoroo/astrid/dao/MetadataDaoTests.java new file mode 100644 index 000000000..f8c7ae559 --- /dev/null +++ b/tests/src/com/todoroo/astrid/dao/MetadataDaoTests.java @@ -0,0 +1,187 @@ +package com.todoroo.astrid.dao; + +import com.todoroo.andlib.service.Autowired; +import com.todoroo.andlib.test.data.Property; +import com.todoroo.andlib.test.data.TodorooCursor; +import com.todoroo.astrid.dao.MetadataDao.MetadataSql; +import com.todoroo.astrid.model.Metadata; +import com.todoroo.astrid.model.Task; +import com.todoroo.astrid.service.MetadataService; +import com.todoroo.astrid.test.DatabaseTestCase; + +public class MetadataDaoTests extends DatabaseTestCase { + + @Autowired + MetadataDao metadataDao; + + @Autowired + TaskDao taskDao; + + public static Property[] KEYS = new Property[] { Metadata.ID, + Metadata.KEY }; + + /** + * Test basic creation, fetch, and save + */ + public void testCrud() throws Exception { + TodorooCursor cursor = metadataDao.fetch(database, + MetadataService.idProperty(), null, null); + assertEquals(0, cursor.getCount()); + cursor.close(); + + // create "happy" + Metadata metadata = new Metadata(); + metadata.setValue(Metadata.KEY, "happy"); + assertTrue(metadataDao.save(database, metadata)); + cursor = metadataDao.fetch(database, MetadataService.idProperty(), null, null); + assertEquals(1, cursor.getCount()); + cursor.close(); + long happyId = metadata.getId(); + assertNotSame(Metadata.NO_ID, happyId); + metadata = metadataDao.fetch(database, KEYS, happyId); + assertEquals("happy", metadata.getValue(Metadata.KEY)); + + // create "sad" + metadata = new Metadata(); + metadata.setValue(Metadata.KEY, "sad"); + assertTrue(metadataDao.save(database, metadata)); + cursor = metadataDao.fetch(database, MetadataService.idProperty(), null, null); + assertEquals(2, cursor.getCount()); + cursor.close(); + + // rename sad to melancholy + long sadId = metadata.getId(); + assertNotSame(Metadata.NO_ID, sadId); + metadata.setValue(Metadata.KEY, "melancholy"); + assertTrue(metadataDao.save(database, metadata)); + cursor = metadataDao.fetch(database, MetadataService.idProperty(), null, null); + assertEquals(2, cursor.getCount()); + cursor.close(); + + // check state + metadata = metadataDao.fetch(database, KEYS, happyId); + assertEquals("happy", metadata.getValue(Metadata.KEY)); + metadata = metadataDao.fetch(database, KEYS, sadId); + assertEquals("melancholy", metadata.getValue(Metadata.KEY)); + + // delete sad + assertTrue(metadataDao.delete(database, sadId)); + cursor = metadataDao.fetch(database, KEYS, null, null); + assertEquals(1, cursor.getCount()); + cursor.moveToFirst(); + metadata.readFromCursor(cursor, KEYS); + assertEquals("happy", metadata.getValue(Metadata.KEY)); + cursor.close(); + } + + /** + * Test metadata bound to task + */ + public void testMetadataConditions() throws Exception { + // create "happy" + Metadata metadata = new Metadata(); + metadata.setValue(Metadata.KEY, "with1"); + metadata.setValue(Metadata.TASK, 1L); + assertTrue(metadataDao.save(database, metadata)); + + metadata = new Metadata(); + metadata.setValue(Metadata.KEY, "with2"); + metadata.setValue(Metadata.TASK, 2L); + assertTrue(metadataDao.save(database, metadata)); + + metadata = new Metadata(); + metadata.setValue(Metadata.KEY, "with1"); + metadata.setValue(Metadata.TASK, 1L); + assertTrue(metadataDao.save(database, metadata)); + + + TodorooCursor cursor = metadataDao.fetch(database, + KEYS, MetadataSql.byTask(1), null); + assertEquals(2, cursor.getCount()); + cursor.moveToFirst(); + metadata.readFromCursor(cursor, KEYS); + assertEquals("with1", metadata.getValue(Metadata.KEY)); + cursor.moveToNext(); + metadata.readFromCursor(cursor, KEYS); + assertEquals("with1", metadata.getValue(Metadata.KEY)); + cursor.close(); + + cursor = metadataDao.fetch(database, + MetadataService.idProperty(), MetadataSql.byTask(3), null); + assertEquals(0, cursor.getCount()); + cursor.close(); + + int deleted = metadataDao.deleteWhere(database, MetadataSql.byTask(1)); + assertEquals(2, deleted); + cursor = metadataDao.fetch(database, + MetadataService.idProperty(), null, null); + assertEquals(1, cursor.getCount()); + cursor.close(); + } + + /** + * Test metadata deletion + */ + public void testFetchDangling() throws Exception { + // fetch with nothing in db + TodorooCursor cursor = metadataDao.fetchDangling(database, KEYS); + assertEquals(0, cursor.getCount()); + cursor.close(); + + Task task1 = new Task(); + taskDao.save(database, task1); + Task task2 = new Task(); + taskDao.save(database, task2); + Task task3 = new Task(); + taskDao.save(database, task3); + + // fetch with only tasks + cursor = metadataDao.fetchDangling(database, KEYS); + assertEquals(0, cursor.getCount()); + cursor.close(); + + Metadata metadata = new Metadata(); + metadata.setValue(Metadata.KEY, "with1"); + metadata.setValue(Metadata.TASK, task1.getId()); + assertTrue(metadataDao.save(database, metadata)); + + metadata = new Metadata(); + metadata.setValue(Metadata.KEY, "with2"); + metadata.setValue(Metadata.TASK, task2.getId()); + assertTrue(metadataDao.save(database, metadata)); + + metadata = new Metadata(); + metadata.setValue(Metadata.KEY, "with3"); + metadata.setValue(Metadata.TASK, task3.getId()); + assertTrue(metadataDao.save(database, metadata)); + + // fetch with tasks and corresponding metadata + cursor = metadataDao.fetchDangling(database, KEYS); + assertEquals(0, cursor.getCount()); + cursor.close(); + + long task2Id = task2.getId(); + taskDao.delete(database, task2.getId()); + + // note: we should not have any dangling, since deleting a task + // will automatically delete metadata + cursor = metadataDao.fetchDangling(database, KEYS); + assertEquals(0, cursor.getCount()); + cursor.close(); + + metadata = new Metadata(); + metadata.setValue(Metadata.KEY, "with2"); + metadata.setValue(Metadata.TASK, task2Id); + assertTrue(metadataDao.save(database, metadata)); + + // but if we simulate something bad happening by creating + // it manually.. well, what can i say, it should be broken + cursor = metadataDao.fetchDangling(database, KEYS); + assertEquals(1, cursor.getCount()); + cursor.moveToFirst(); + metadata.readFromCursor(cursor, KEYS); + assertEquals("with2", metadata.getValue(Metadata.KEY)); + cursor.close(); + } + +} \ No newline at end of file diff --git a/tests/src/com/todoroo/astrid/dao/TaskDaoTests.java b/tests/src/com/todoroo/astrid/dao/TaskDaoTests.java new file mode 100644 index 000000000..f10d45f56 --- /dev/null +++ b/tests/src/com/todoroo/astrid/dao/TaskDaoTests.java @@ -0,0 +1,216 @@ +package com.todoroo.astrid.dao; + +import com.todoroo.andlib.service.Autowired; +import com.todoroo.andlib.test.data.Property; +import com.todoroo.andlib.test.data.TodorooCursor; +import com.todoroo.andlib.test.utility.DateUtilities; +import com.todoroo.astrid.dao.TaskDao.TaskSql; +import com.todoroo.astrid.model.Task; +import com.todoroo.astrid.test.DatabaseTestCase; + +public class TaskDaoTests extends DatabaseTestCase { + + public static Property[] IDS = new Property[] { Task.ID }; + + public static Property[] TITLES = new Property[] { Task.ID, + Task.TITLE }; + + @Autowired + TaskDao taskDao; + + /** + * Test basic task creation, fetch, and save + */ + public void testTaskCreation() throws Exception { + TodorooCursor cursor = taskDao.fetch(database, + IDS, null, null, null); + assertEquals(0, cursor.getCount()); + cursor.close(); + + // create task "happy" + Task task = new Task(); + task.setValue(Task.TITLE, "happy"); + assertTrue(taskDao.save(database, task, false)); + cursor = taskDao.fetch(database, IDS, null, null, null); + assertEquals(1, cursor.getCount()); + cursor.close(); + long happyId = task.getId(); + assertNotSame(Task.NO_ID, happyId); + task = taskDao.fetch(database, TITLES, happyId); + assertEquals("happy", task.getValue(Task.TITLE)); + + // create task "sad" + task = new Task(); + task.setValue(Task.TITLE, "sad"); + assertTrue(taskDao.save(database, task, false)); + cursor = taskDao.fetch(database, IDS, null, null, null); + assertEquals(2, cursor.getCount()); + cursor.close(); + + // rename sad to melancholy + long sadId = task.getId(); + assertNotSame(Task.NO_ID, sadId); + task.setValue(Task.TITLE, "melancholy"); + assertTrue(taskDao.save(database, task, false)); + cursor = taskDao.fetch(database, IDS, null, null, null); + assertEquals(2, cursor.getCount()); + cursor.close(); + + // check state + task = taskDao.fetch(database, TITLES, happyId); + assertEquals("happy", task.getValue(Task.TITLE)); + task = taskDao.fetch(database, TITLES, sadId); + assertEquals("melancholy", task.getValue(Task.TITLE)); + } + + /** + * Test various task fetch conditions + */ + public void testTaskConditions() throws Exception { + // create normal task + Task task = new Task(); + task.setValue(Task.TITLE, "normal"); + assertTrue(taskDao.save(database, task, false)); + + // create blank task + task = new Task(); + task.setValue(Task.TITLE, ""); + assertTrue(taskDao.save(database, task, false)); + + // create hidden task + task = new Task(); + task.setValue(Task.TITLE, "hidden"); + task.setValue(Task.HIDDEN_UNTIL, DateUtilities.now() + 10000); + assertTrue(taskDao.save(database, task, false)); + + // create task with deadlines + task = new Task(); + task.setValue(Task.TITLE, "deadlineInFuture"); + task.setValue(Task.DUE_DATE, DateUtilities.now() + 10000); + assertTrue(taskDao.save(database, task, false)); + + task = new Task(); + task.setValue(Task.TITLE, "deadlineInPast"); + task.setValue(Task.DUE_DATE, DateUtilities.now() - 10000); + assertTrue(taskDao.save(database, task, false)); + + // create completed task + task = new Task(); + task.setValue(Task.TITLE, "completed"); + task.setValue(Task.COMPLETION_DATE, DateUtilities.now() - 10000); + assertTrue(taskDao.save(database, task, false)); + + // check has no name + TodorooCursor cursor = taskDao.fetch(database, + TITLES, TaskSql.hasNoName(), null, null); + assertEquals(1, cursor.getCount()); + cursor.moveToNext(); + assertEquals("", cursor.getString(1)); + cursor.close(); + + // check has deadlines + cursor = taskDao.fetch(database, TITLES, TaskSql + .hasDeadlines(), Task.DUE_DATE + " ASC", null); + assertEquals(2, cursor.getCount()); + cursor.moveToNext(); + assertEquals("deadlineInPast", cursor.getString(1)); + cursor.moveToNext(); + assertEquals("deadlineInFuture", cursor.getString(1)); + cursor.close(); + + // check is active + cursor = taskDao.fetch(database, TITLES, TaskSql + .isActive(), null, null); + assertEquals(5, cursor.getCount()); + cursor.close(); + + // check due before / after + cursor = taskDao.fetch(database, TITLES, TaskSql + .dueBefore(DateUtilities.now()), null, null); + cursor.moveToNext(); + assertEquals(1, cursor.getCount()); + cursor.close(); + cursor = taskDao.fetch(database, TITLES, TaskSql + .dueAfter(DateUtilities.now()), null, null); + assertEquals(1, cursor.getCount()); + cursor.close(); + + // check completed before + cursor = taskDao.fetch(database, TITLES, TaskSql + .completedBefore(DateUtilities.now()), null, null); + assertEquals(1, cursor.getCount()); + cursor.close(); + + // check is visible + cursor = taskDao.fetch(database, TITLES, TaskSql + .isVisible(DateUtilities.now()), null, null); + assertEquals(5, cursor.getCount()); + cursor.close(); + } + + /** + * Test task deletion + */ + public void testTDeletion() throws Exception { + TodorooCursor cursor = taskDao.fetch(database, + IDS, null, null, null); + assertEquals(0, cursor.getCount()); + cursor.close(); + + // create task "happy" + Task task = new Task(); + task.setValue(Task.TITLE, "happy"); + assertTrue(taskDao.save(database, task, false)); + cursor = taskDao.fetch(database, IDS, null, null, null); + assertEquals(1, cursor.getCount()); + cursor.close(); + + // delete + long happyId = task.getId(); + assertTrue(taskDao.delete(database, happyId)); + cursor = taskDao.fetch(database, IDS, null, null, null); + assertEquals(0, cursor.getCount()); + cursor.close(); + } + + /** + * Test save without prior create doesn't work + */ + public void testSaveWithoutCreate() throws Exception { + TodorooCursor cursor; + + // try to save task "happy" + Task task = new Task(); + task.setValue(Task.TITLE, "happy"); + task.setValue(Task.ID, 1L); + + assertFalse(taskDao.save(database, task, false)); + + cursor = taskDao.fetch(database, IDS, null, null, null); + assertEquals(0, cursor.getCount()); + cursor.close(); + } + + /** + * Test passing invalid task indices to various things + */ + public void testInvalidIndex() throws Exception { + TodorooCursor cursor; + + cursor = taskDao.fetch(database, IDS, null, null, null); + assertEquals(0, cursor.getCount()); + cursor.close(); + + assertNull(taskDao.fetch(database, IDS, 1)); + + assertFalse(taskDao.delete(database, 1)); + + // make sure db still works + cursor = taskDao.fetch(database, IDS, null, null, null); + assertEquals(0, cursor.getCount()); + cursor.close(); + } + + // TODO check eventing +} + diff --git a/tests/src/com/todoroo/astrid/legacy/data/AbstractController.java b/tests/src/com/todoroo/astrid/legacy/data/AbstractController.java new file mode 100644 index 000000000..f2302b0bf --- /dev/null +++ b/tests/src/com/todoroo/astrid/legacy/data/AbstractController.java @@ -0,0 +1,94 @@ +/* + * ASTRID: Android's Simple Task Recording Dashboard + * + * Copyright (c) 2009 Tim Su + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ +package com.todoroo.astrid.legacy.data; + +import java.lang.reflect.InvocationTargetException; +import java.util.Iterator; + +import com.timsu.astrid.data.AbstractModel; + +import android.content.Context; +import android.database.Cursor; +import android.util.Log; + +/** Abstract controller class. Mostly contains some static fields */ +abstract public class AbstractController { + + protected Context context; + + // special columns + public static final String KEY_ROWID = "_id"; + + // database and table names + protected static final String TASK_TABLE_NAME = "tasks"; + protected static final String TAG_TABLE_NAME = "tags"; + protected static final String TAG_TASK_MAP_NAME = "tagTaskMap"; + protected static final String ALERT_TABLE_NAME = "alerts"; + protected static final String SYNC_TABLE_NAME = "sync"; + + abstract public void open(); + abstract public void close(); + + // cursor iterator + + public static class CursorIterator implements Iterator { + Cursor cursor; + Class cls; + + public CursorIterator(Cursor cursor, Class cls) { + this.cursor = cursor; + this.cls = cls; + } + + public boolean hasNext() { + return !cursor.isLast(); + } + + public TYPE next() { + try { + TYPE model = cls.getConstructor(Cursor.class).newInstance(cursor); + cursor.moveToNext(); + return model; + + // ugh... + } catch (IllegalArgumentException e) { + Log.e("CursorIterator", e.toString()); + } catch (SecurityException e) { + Log.e("CursorIterator", e.toString()); + } catch (InstantiationException e) { + Log.e("CursorIterator", e.toString()); + } catch (IllegalAccessException e) { + Log.e("CursorIterator", e.toString()); + } catch (InvocationTargetException e) { + Log.e("CursorIterator", e.toString()); + } catch (NoSuchMethodException e) { + Log.e("CursorIterator", e.toString()); + } + + return null; + } + + public void remove() { + throw new UnsupportedOperationException("Can't remove this way"); + } + + } + +} diff --git a/tests/src/com/todoroo/astrid/legacy/data/AbstractModel.java b/tests/src/com/todoroo/astrid/legacy/data/AbstractModel.java new file mode 100644 index 000000000..5724a3f19 --- /dev/null +++ b/tests/src/com/todoroo/astrid/legacy/data/AbstractModel.java @@ -0,0 +1,263 @@ +/* + * ASTRID: Android's Simple Task Recording Dashboard + * + * Copyright (c) 2009 Tim Su + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ +package com.todoroo.astrid.legacy.data; + +import java.util.Date; +import java.util.HashMap; + +import android.content.ContentValues; +import android.database.Cursor; + +/** A data object backed by a database */ +public abstract class AbstractModel { + + /* Data Source Ordering: + * + * In order to return the best data, we want to check first what the user + * has explicitly set (setValues), then the values we have read out of + * the database (values), the database itself (cursor), then defaults + * (getDefaultValues) + */ + + /** User set values */ + protected ContentValues setValues = new ContentValues(); + + /** Cached values from database */ + private ContentValues values = new ContentValues(); + + /** Cursor into the database */ + private Cursor cursor = null; + + // --- constructors + + /** Construct a model from scratch */ + public AbstractModel() { + // ... + } + + /** Construct a model from a database object */ + public AbstractModel(Cursor cursor) { + this.cursor = cursor; + } + + // --- data source getters + + /** Get the user-set values for this object */ + public ContentValues getSetValues() { + return setValues; + } + + /** Get the default values for this object */ + abstract public ContentValues getDefaultValues(); + + /** Get a list of all field/value pairs merged across data sources */ + public ContentValues getMergedValues() { + ContentValues mergedValues = new ContentValues(); + + mergedValues.putAll(getDefaultValues()); + mergedValues.putAll(values); + mergedValues.putAll(setValues); + + return mergedValues; + } + + /** Return the database cursor */ + public Cursor getCursor() { + return cursor; + } + + // --- checking against cached values + + protected void putIfChangedFromDatabase(String field, String newValue) { + if(!setValues.containsKey(field) && values.containsKey(field)) { + String value = values.getAsString(field); + if(value == null) { + if(newValue == null) + return; + } else if(value.equals(newValue)) + return; + } + setValues.put(field, newValue); + } + + protected void putIfChangedFromDatabase(String field, Long newValue) { + if(!setValues.containsKey(field) && values.containsKey(field)) { + Long value = values.getAsLong(field); + if(value == null) { + if(newValue == null) + return; + } else if(value.equals(newValue)) + return; + } + setValues.put(field, newValue); + } + + protected void putIfChangedFromDatabase(String field, Integer newValue) { + if(!setValues.containsKey(field) && values.containsKey(field)) { + Integer value = values.getAsInteger(field); + if(value == null) { + if(newValue == null) + return; + } else if(value.equals(newValue)) + return; + } + setValues.put(field, newValue); + } + + protected void putIfChangedFromDatabase(String field, Double newValue) { + if(!setValues.containsKey(field) && values.containsKey(field)) { + Double value = values.getAsDouble(field); + if(value == null) { + if(newValue == null) + return; + } else if(value.equals(newValue)) + return; + } + setValues.put(field, newValue); + } + + protected static final HashMap, HashMap> + columnIndexCache = new HashMap, HashMap>(); + private int getColumnIndex(String field) { + HashMap classCache; + classCache = columnIndexCache.get(getClass()); + if(classCache == null) { + classCache = new HashMap(); + columnIndexCache.put(getClass(), classCache); + } + + Integer index = classCache.get(field); + if(index == null) { + index = cursor.getColumnIndexOrThrow(field); + classCache.put(field, index); + } + + return index; + } + + // --- data retrieval for the different object types + + protected String retrieveString(String field) { + if(setValues.containsKey(field)) + return setValues.getAsString(field); + + if(values.containsKey(field)) + return values.getAsString(field); + + // if we have a database to hit, do that now + if(cursor != null) { + String value = cursor.getString(getColumnIndex(field)); + values.put(field, value); + return value; + } + + // do we have defaults? + ContentValues defaults = getDefaultValues(); + if(defaults != null && defaults.containsKey(field)) + return defaults.getAsString(field); + + throw new UnsupportedOperationException("Could not read field " + field); + } + + protected Integer retrieveInteger(String field) { + if(setValues.containsKey(field)) + return setValues.getAsInteger(field); + + if(values.containsKey(field)) + return values.getAsInteger(field); + + // if we have a database to hit, do that now + if(cursor != null) { + try { + Integer value = cursor.getInt(getColumnIndex(field)); + values.put(field, value); + return value; + } catch (Exception e) { + // error reading from cursor, try to continue + } + } + + // do we have defaults? + ContentValues defaults = getDefaultValues(); + if(defaults != null && defaults.containsKey(field)) + return defaults.getAsInteger(field); + + throw new UnsupportedOperationException("Could not read field " + field); + } + + protected Long retrieveLong(String field) { + if(setValues.containsKey(field)) + return setValues.getAsLong(field); + + if(values.containsKey(field)) + return values.getAsLong(field); + + // if we have a database to hit, do that now + if(cursor != null) { + Long value = cursor.getLong(getColumnIndex(field)); + values.put(field, value); + return value; + } + + // do we have defaults? + ContentValues defaults = getDefaultValues(); + if(defaults != null && defaults.containsKey(field)) + return defaults.getAsLong(field); + + throw new UnsupportedOperationException("Could not read field " + field); + } + + protected Double retrieveDouble(String field) { + if(setValues.containsKey(field)) + return setValues.getAsDouble(field); + + if(values.containsKey(field)) + return values.getAsDouble(field); + + // if we have a database to hit, do that now + if(cursor != null) { + Double value = cursor.getDouble(getColumnIndex(field)); + values.put(field, value); + return value; + } + + // do we have defaults? + ContentValues defaults = getDefaultValues(); + if(defaults != null && defaults.containsKey(field)) + return defaults.getAsDouble(field); + + throw new UnsupportedOperationException("Could not read field " + field); + } + + // --- retrieving composite objects + + protected Date retrieveDate(String field) { + Long time; + try { + time = retrieveLong(field); + if(time == null || time == 0) + return null; + } catch (NullPointerException e) { + return null; + } + + return new Date(time); + } +} diff --git a/tests/src/com/todoroo/astrid/legacy/data/Identifier.java b/tests/src/com/todoroo/astrid/legacy/data/Identifier.java new file mode 100644 index 000000000..4e5c5e6c3 --- /dev/null +++ b/tests/src/com/todoroo/astrid/legacy/data/Identifier.java @@ -0,0 +1,56 @@ +/* + * ASTRID: Android's Simple Task Recording Dashboard + * + * Copyright (c) 2009 Tim Su + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ +package com.todoroo.astrid.legacy.data; + + +/** Identifier of a single object. Extend this class to create your own */ +public abstract class Identifier { + private long id; + + public Identifier(long id) { + this.id = id; + } + + public long getId() { + return id; + } + + public String idAsString() { + return Long.toString(id); + } + + @Override + public int hashCode() { + return (int)id; + } + + @Override + public boolean equals(Object o) { + if(o == null || o.getClass() != getClass()) + return false; + + return ((Identifier)o).getId() == getId(); + } + + @Override + public String toString() { + return getClass().getSimpleName() + ": " + id; + } +} diff --git a/tests/src/com/todoroo/astrid/legacy/data/alerts/Alert.java b/tests/src/com/todoroo/astrid/legacy/data/alerts/Alert.java new file mode 100644 index 000000000..7778fddab --- /dev/null +++ b/tests/src/com/todoroo/astrid/legacy/data/alerts/Alert.java @@ -0,0 +1,132 @@ +/* + * ASTRID: Android's Simple Task Recording Dashboard + * + * Copyright (c) 2009 Tim Su + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ +package com.todoroo.astrid.legacy.data.alerts; + +import java.util.Date; + +import android.content.ContentValues; +import android.content.Context; +import android.database.Cursor; +import android.database.sqlite.SQLiteDatabase; +import android.database.sqlite.SQLiteOpenHelper; +import android.util.Log; + +import com.todoroo.astrid.legacy.data.AbstractController; +import com.todoroo.astrid.legacy.data.AbstractModel; +import com.todoroo.astrid.legacy.data.task.TaskIdentifier; + + +/** A single alert on a task */ +public class Alert extends AbstractModel { + + /** Version number of this model */ + static final int VERSION = 1; + + // field names + + static final String TASK = "task"; + static final String DATE = "date"; + + /** Default values container */ + private static final ContentValues defaultValues = new ContentValues(); + + @Override + public ContentValues getDefaultValues() { + return defaultValues; + } + + static String[] FIELD_LIST = new String[] { + AbstractController.KEY_ROWID, + TASK, + DATE, + }; + + // --- database helper + + /** Database Helper manages creating new tables and updating old ones */ + static class AlertDatabaseHelper extends SQLiteOpenHelper { + String tableName; + Context context; + + AlertDatabaseHelper(Context context, String databaseName, String tableName) { + super(context, databaseName, null, VERSION); + this.tableName = tableName; + this.context = context; + } + + @Override + public synchronized void onCreate(SQLiteDatabase db) { + String sql = new StringBuilder(). + append("CREATE TABLE IF NOT EXISTS ").append(tableName).append(" ("). + append(AbstractController.KEY_ROWID).append(" integer primary key autoincrement, "). + append(TASK).append(" integer not null,"). + append(DATE).append(" integer not null,"). + append("unique (").append(TASK).append(",").append(DATE).append(")"). + append(");").toString(); + db.execSQL(sql); + } + + @Override + public synchronized void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) { + Log.w(getClass().getSimpleName(), "Upgrading database from version " + + oldVersion + " to " + newVersion + "."); + + switch(oldVersion) { + default: + throw new RuntimeException("Alerts: Unsupported migration from " + oldVersion + " to " + newVersion); + } + } + } + + + // --- constructor pass-through + + Alert(TaskIdentifier task, Date date) { + super(); + setTask(task); + setDate(date); + } + + public Alert(Cursor cursor) { + super(cursor); + } + + // --- getters and setters: expose them as you see fit + + public boolean isNew() { + return getCursor() == null; + } + + public TaskIdentifier getTask() { + return new TaskIdentifier(retrieveLong(TASK)); + } + + public Date getDate() { + return new Date(retrieveLong(DATE)); + } + + private void setTask(TaskIdentifier task) { + setValues.put(TASK, task.getId()); + } + + private void setDate(Date date) { + setValues.put(DATE, date.getTime()); + } +} diff --git a/tests/src/com/todoroo/astrid/legacy/data/alerts/AlertController.java b/tests/src/com/todoroo/astrid/legacy/data/alerts/AlertController.java new file mode 100644 index 000000000..d42da70b0 --- /dev/null +++ b/tests/src/com/todoroo/astrid/legacy/data/alerts/AlertController.java @@ -0,0 +1,144 @@ +/* + * ASTRID: Android's Simple Task Recording Dashboard + * + * Copyright (c) 2009 Tim Su + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ +package com.todoroo.astrid.legacy.data.alerts; + +import java.util.Date; +import java.util.HashSet; +import java.util.LinkedList; +import java.util.List; +import java.util.Set; + +import android.content.ContentValues; +import android.content.Context; +import android.database.Cursor; +import android.database.SQLException; +import android.database.sqlite.SQLiteDatabase; + +import com.todoroo.astrid.legacy.data.AbstractController; +import com.todoroo.astrid.legacy.data.alerts.Alert.AlertDatabaseHelper; +import com.todoroo.astrid.legacy.data.task.TaskIdentifier; + +/** Controller for Tag-related operations */ +public class AlertController extends AbstractController { + + private SQLiteDatabase alertDatabase; + + /** Get a cursor to tag identifiers */ + public Cursor getTaskAlertsCursor(TaskIdentifier taskId) throws SQLException { + Cursor cursor = alertDatabase.query(ALERT_TABLE_NAME, + Alert.FIELD_LIST, Alert.TASK + " = ?", + new String[] { taskId.idAsString() }, null, null, null); + return cursor; + } + + /** Get a list of alerts for the given task */ + public List getTaskAlerts(TaskIdentifier + taskId) throws SQLException { + List list = new LinkedList(); + Cursor cursor = alertDatabase.query(ALERT_TABLE_NAME, + Alert.FIELD_LIST, Alert.TASK + " = ?", + new String[] { taskId.idAsString() }, null, null, null); + + try { + if(cursor.getCount() == 0) + return list; + do { + cursor.moveToNext(); + list.add(new Alert(cursor).getDate()); + } while(!cursor.isLast()); + + return list; + } finally { + cursor.close(); + } + } + + + /** Get a list of alerts that are set for the future */ + public Set getTasksWithActiveAlerts() throws SQLException { + Set list = new HashSet(); + Cursor cursor = alertDatabase.query(ALERT_TABLE_NAME, + Alert.FIELD_LIST, Alert.DATE + " > ?", + new String[] { Long.toString(System.currentTimeMillis()) }, null, null, null); + + try { + if(cursor.getCount() == 0) + return list; + do { + cursor.moveToNext(); + list.add(new Alert(cursor).getTask()); + } while(!cursor.isLast()); + + return list; + } finally { + cursor.close(); + } + } + + /** Remove all alerts from the task */ + public boolean removeAlerts(TaskIdentifier taskId) + throws SQLException{ + return alertDatabase.delete(ALERT_TABLE_NAME, + String.format("%s = ?", + Alert.TASK), + new String[] { taskId.idAsString() }) > 0; + } + + /** Add the given tag to the task */ + public boolean addAlert(TaskIdentifier taskId, Date date) + throws SQLException { + ContentValues values = new ContentValues(); + values.put(Alert.DATE, date.getTime()); + values.put(Alert.TASK, taskId.getId()); + return alertDatabase.insert(ALERT_TABLE_NAME, Alert.TASK, + values) >= 0; + } + + // --- boilerplate + + /** + * Constructor - takes the context to allow the database to be + * opened/created + */ + public AlertController(Context context) { + this.context = context; + } + + /** + * Open the notes database. If it cannot be opened, try to create a new + * instance of the database. If it cannot be created, throw an exception to + * signal the failure + * + * @return this (self reference, allowing this to be chained in an + * initialization call) + * @throws SQLException if the database could be neither opened or created + */ + @Override + public void open() throws SQLException { + alertDatabase = new AlertDatabaseHelper(context, + ALERT_TABLE_NAME, ALERT_TABLE_NAME).getWritableDatabase(); + } + + /** Closes database resource */ + @Override + public void close() { + alertDatabase.close(); + } +} diff --git a/tests/src/com/todoroo/astrid/legacy/data/enums/Importance.java b/tests/src/com/todoroo/astrid/legacy/data/enums/Importance.java new file mode 100644 index 000000000..1a40b2a21 --- /dev/null +++ b/tests/src/com/todoroo/astrid/legacy/data/enums/Importance.java @@ -0,0 +1,66 @@ +/* + * ASTRID: Android's Simple Task Recording Dashboard + * + * Copyright (c) 2009 Tim Su + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ +package com.todoroo.astrid.legacy.data.enums; + +import com.timsu.astrid.R; + +public enum Importance { + // MOST IMPORTANT + + LEVEL_1(R.string.importance_1, + R.color.importance_1, + R.color.task_list_importance_1), + LEVEL_2(R.string.importance_2, + R.color.importance_2, + R.color.task_list_importance_2), + LEVEL_3(R.string.importance_3, + R.color.importance_3, + R.color.task_list_importance_3), + LEVEL_4(R.string.importance_4, + R.color.importance_4, + R.color.task_list_importance_4), + + // LEAST IMPORTANT + ; + + int label; + int color; + int taskListColor; + public static final Importance DEFAULT = LEVEL_3; + + private Importance(int label, int color, int taskListColor) { + this.label = label; + this.color = color; + this.taskListColor = taskListColor; + } + + public int getLabelResource() { + return label; + } + + public int getColorResource() { + return color; + } + + public int getTaskListColor() { + return taskListColor; + } + +} diff --git a/tests/src/com/todoroo/astrid/legacy/data/enums/RepeatInterval.java b/tests/src/com/todoroo/astrid/legacy/data/enums/RepeatInterval.java new file mode 100644 index 000000000..ecf9bed47 --- /dev/null +++ b/tests/src/com/todoroo/astrid/legacy/data/enums/RepeatInterval.java @@ -0,0 +1,77 @@ +/* + * ASTRID: Android's Simple Task Recording Dashboard + * + * Copyright (c) 2009 Tim Su + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ +package com.todoroo.astrid.legacy.data.enums; + +import java.util.Date; + +import android.content.res.Resources; + +import com.timsu.astrid.R; + +public enum RepeatInterval { + + DAYS(R.string.repeat_days) { + @Override + public void offsetDateBy(Date input, int number) { + input.setDate(input.getDate() + number); + } + }, + WEEKS(R.string.repeat_weeks) { + @Override + public void offsetDateBy(Date input, int number) { + input.setDate(input.getDate() + 7 * number); + } + }, + MONTHS(R.string.repeat_months) { + @Override + public void offsetDateBy(Date input, int number) { + input.setMonth(input.getMonth() + number); + } + }, + HOURS(R.string.repeat_hours) { + @Override + public void offsetDateBy(Date input, int number) { + input.setHours(input.getHours() + number); + } + }, + + ; + + int label; + + private RepeatInterval(int label) { + this.label = label; + } + + public int getLabelResource() { + return label; + } + + abstract public void offsetDateBy(Date input, int number); + + public static String[] getLabels(Resources r) { + int intervalCount = RepeatInterval.values().length; + String[] result = new String[intervalCount]; + + for(int i = 0; i < intervalCount; i++) + result[i] = r.getString(RepeatInterval.values()[i].getLabelResource()); + return result; + } +} diff --git a/tests/src/com/todoroo/astrid/legacy/data/sync/SyncDataController.java b/tests/src/com/todoroo/astrid/legacy/data/sync/SyncDataController.java new file mode 100644 index 000000000..fa6104dce --- /dev/null +++ b/tests/src/com/todoroo/astrid/legacy/data/sync/SyncDataController.java @@ -0,0 +1,195 @@ +/* + * ASTRID: Android's Simple Task Recording Dashboard + * + * Copyright (c) 2009 Tim Su + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ +package com.todoroo.astrid.legacy.data.sync; + +import java.util.HashSet; + +import android.content.ContentValues; +import android.content.Context; +import android.database.Cursor; +import android.database.SQLException; +import android.database.sqlite.SQLiteDatabase; +import android.database.sqlite.SQLiteOpenHelper; + +import com.todoroo.astrid.legacy.data.AbstractController; +import com.todoroo.astrid.legacy.data.sync.SyncMapping.SyncMappingDatabaseHelper; +import com.todoroo.astrid.legacy.data.task.AbstractTaskModel; +import com.todoroo.astrid.legacy.data.task.TaskIdentifier; +import com.todoroo.astrid.legacy.data.task.TaskModelForSync; + +/** Controller for Tag-related operations */ +public class SyncDataController extends AbstractController { + + private SQLiteDatabase syncDatabase; + + + // --- updated tasks list + + /** Mark all updated tasks as finished synchronizing */ + public boolean clearUpdatedTaskList(int syncServiceId) throws SQLException { + ContentValues values = new ContentValues(); + values.put(SyncMapping.UPDATED, 0); + return syncDatabase.update(SYNC_TABLE_NAME, values, + SyncMapping.SYNC_SERVICE + " = " + syncServiceId, null) > 0; + } + + /** Indicate that this task's properties were updated */ + public boolean addToUpdatedList(TaskIdentifier taskId) throws SQLException { + ContentValues values = new ContentValues(); + values.put(SyncMapping.UPDATED, 1); + return syncDatabase.update(SYNC_TABLE_NAME, values, + SyncMapping.TASK + " = " + taskId.getId(), null) > 0; + } + + public static void taskUpdated(Context context, AbstractTaskModel task) { + if(!(task instanceof TaskModelForSync)) { + SyncDataController syncController = new SyncDataController(context); + syncController.open(); + syncController.addToUpdatedList(task.getTaskIdentifier()); + syncController.close(); + } + } + + // --- sync mapping + + /** Get all mappings for the given synchronization service */ + public HashSet getSyncMappings(int syncServiceId) throws SQLException { + HashSet list = new HashSet(); + Cursor cursor = syncDatabase.query(SYNC_TABLE_NAME, + SyncMapping.FIELD_LIST, + SyncMapping.SYNC_SERVICE + " = " + syncServiceId, + null, null, null, null); + + try { + if(cursor.getCount() == 0) + return list; + do { + cursor.moveToNext(); + list.add(new SyncMapping(cursor)); + } while(!cursor.isLast()); + + return list; + } finally { + cursor.close(); + } + } + + /** Get all mappings for specified task for all synchronization services */ + public HashSet getSyncMappings(TaskIdentifier taskId) + throws SQLException { + HashSet list = new HashSet(); + Cursor cursor = syncDatabase.query(SYNC_TABLE_NAME, + SyncMapping.FIELD_LIST, + SyncMapping.TASK + " = ?", + new String[] { "" + taskId.getId() }, + null, null, null); + + try { + if(cursor.getCount() == 0) + return list; + do { + cursor.moveToNext(); + list.add(new SyncMapping(cursor)); + } while(!cursor.isLast()); + + return list; + } finally { + cursor.close(); + } + } + + /** Get mapping for given task */ + public SyncMapping getSyncMapping(int syncServiceId, TaskIdentifier taskId) + throws SQLException { + Cursor cursor = syncDatabase.query(SYNC_TABLE_NAME, + SyncMapping.FIELD_LIST, + SyncMapping.SYNC_SERVICE + " = ? AND " + + SyncMapping.TASK + " = ?", + new String[] { "" + syncServiceId, "" + taskId.getId() }, + null, null, null); + + try { + if(cursor.getCount() == 0) + return null; + cursor.moveToNext(); + return new SyncMapping(cursor); + } finally { + cursor.close(); + } + } + + /** Saves the given task to the database. Returns true on success. */ + public boolean saveSyncMapping(SyncMapping mapping) { + long newRow = syncDatabase.insert(SYNC_TABLE_NAME, SyncMapping.TASK, + mapping.getMergedValues()); + + mapping.setId(newRow); + + return newRow >= 0; + } + + /** Deletes the given mapping. Returns true on success */ + public boolean deleteSyncMapping(SyncMapping mapping) { + // was never saved + if(mapping.getId() == 0) + return false; + + return syncDatabase.delete(SYNC_TABLE_NAME, KEY_ROWID + "=" + + mapping.getId(), null) > 0; + } + + /** Deletes the given mapping. Returns true on success */ + public boolean deleteAllMappings(int syncServiceId) { + return syncDatabase.delete(SYNC_TABLE_NAME, SyncMapping.SYNC_SERVICE + + "=" + syncServiceId, null) > 0; + } + + // --- boilerplate + + /** + * Constructor - takes the context to allow the database to be + * opened/created + */ + public SyncDataController(Context context) { + this.context = context; + } + + /** + * Open the notes database. If it cannot be opened, try to create a new + * instance of the database. If it cannot be created, throw an exception to + * signal the failure + * + * @return this (self reference, allowing this to be chained in an + * initialization call) + * @throws SQLException if the database could be neither opened or created + */ + @Override + public synchronized void open() throws SQLException { + SQLiteOpenHelper helper = new SyncMappingDatabaseHelper(context, + SYNC_TABLE_NAME, SYNC_TABLE_NAME); + syncDatabase = helper.getWritableDatabase(); + } + + /** Closes database resource */ + @Override + public void close() { + syncDatabase.close(); + } +} diff --git a/tests/src/com/todoroo/astrid/legacy/data/sync/SyncMapping.java b/tests/src/com/todoroo/astrid/legacy/data/sync/SyncMapping.java new file mode 100644 index 000000000..331c6a7ae --- /dev/null +++ b/tests/src/com/todoroo/astrid/legacy/data/sync/SyncMapping.java @@ -0,0 +1,166 @@ +/* + * ASTRID: Android's Simple Task Recording Dashboard + * + * Copyright (c) 2009 Tim Su + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ +package com.todoroo.astrid.legacy.data.sync; + +import android.content.ContentValues; +import android.content.Context; +import android.database.Cursor; +import android.database.sqlite.SQLiteDatabase; +import android.database.sqlite.SQLiteOpenHelper; +import android.util.Log; + +import com.todoroo.astrid.legacy.data.AbstractController; +import com.todoroo.astrid.legacy.data.AbstractModel; +import com.todoroo.astrid.legacy.data.task.TaskIdentifier; + + +/** A single tag on a task */ +public class SyncMapping extends AbstractModel { + + + /** Version number of this model */ + static final int VERSION = 1; + + // field names + + static final String TASK = "task"; + static final String SYNC_SERVICE = "service"; + static final String REMOTE_ID = "remoteId"; + static final String UPDATED = "updated"; + + /** Default values container */ + private static final ContentValues defaultValues = new ContentValues(); + static { + defaultValues.put(UPDATED, 0); + } + + @Override + public ContentValues getDefaultValues() { + return defaultValues; + } + + static String[] FIELD_LIST = new String[] { + AbstractController.KEY_ROWID, + TASK, + SYNC_SERVICE, + REMOTE_ID, + UPDATED, + }; + + // --- database helper + + /** Database Helper manages creating new tables and updating old ones */ + static class SyncMappingDatabaseHelper extends SQLiteOpenHelper { + String tableName; + Context context; + + SyncMappingDatabaseHelper(Context context, String databaseName, String tableName) { + super(context, databaseName, null, VERSION); + this.tableName = tableName; + this.context = context; + } + + @Override + public synchronized void onCreate(SQLiteDatabase db) { + String sql = new StringBuilder(). + append("CREATE TABLE IF NOT EXISTS ").append(tableName).append(" ("). + append(AbstractController.KEY_ROWID).append(" integer primary key autoincrement, "). + append(TASK).append(" integer not null,"). + append(SYNC_SERVICE).append(" integer not null,"). + append(REMOTE_ID).append(" text not null,"). + append(UPDATED).append(" integer not null,"). + append("unique (").append(TASK).append(",").append(SYNC_SERVICE).append(")"). + append(");").toString(); + db.execSQL(sql); + } + + @Override + public synchronized void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) { + Log.w(getClass().getSimpleName(), "Upgrading database from version " + + oldVersion + " to " + newVersion + "."); + + switch(oldVersion) { + default: + throw new RuntimeException("Sync: Unsupported migration from " + oldVersion + " to " + newVersion); + } + } + } + + + // --- constructor pass-through + + public SyncMapping(TaskIdentifier task, int syncServiceId, String remoteId) { + super(); + setTask(task); + setSyncServiceId(syncServiceId); + setRemoteId(remoteId); + } + + SyncMapping(Cursor cursor) { + super(cursor); + getId(); + getTask(); + getSyncServiceId(); + getRemoteId(); + isUpdated(); + } + + // --- getters and setters + + public void setId(long id) { + putIfChangedFromDatabase(AbstractController.KEY_ROWID, id); + } + + public long getId() { + try { + return retrieveLong(AbstractController.KEY_ROWID); + } catch (UnsupportedOperationException e) { + return 0; + } + } + + public TaskIdentifier getTask() { + return new TaskIdentifier(retrieveLong(TASK)); + } + + public int getSyncServiceId() { + return retrieveInteger(SYNC_SERVICE); + } + + public String getRemoteId() { + return retrieveString(REMOTE_ID); + } + + public boolean isUpdated() { + return retrieveInteger(UPDATED) == 1; + } + + private void setTask(TaskIdentifier task) { + setValues.put(TASK, task.getId()); + } + + private void setSyncServiceId(int id) { + setValues.put(SYNC_SERVICE, id); + } + + private void setRemoteId(String remoteId) { + setValues.put(REMOTE_ID, remoteId); + } +} diff --git a/tests/src/com/todoroo/astrid/legacy/data/tag/AbstractTagModel.java b/tests/src/com/todoroo/astrid/legacy/data/tag/AbstractTagModel.java new file mode 100644 index 000000000..8c82352f0 --- /dev/null +++ b/tests/src/com/todoroo/astrid/legacy/data/tag/AbstractTagModel.java @@ -0,0 +1,196 @@ +/* + * ASTRID: Android's Simple Task Recording Dashboard + * + * Copyright (c) 2009 Tim Su + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ +package com.todoroo.astrid.legacy.data.tag; + +import java.util.Date; + +import android.content.ContentValues; +import android.content.Context; +import android.database.Cursor; +import android.database.sqlite.SQLiteDatabase; +import android.database.sqlite.SQLiteOpenHelper; +import android.util.Log; + +import com.todoroo.astrid.legacy.data.AbstractController; +import com.todoroo.astrid.legacy.data.AbstractModel; + + +/** Abstract model of a task. Subclasses implement the getters and setters + * they are interested in. + * + * @author timsu + * + */ +public abstract class AbstractTagModel extends AbstractModel { + + /** Version number of this model */ + static final int VERSION = 1; + + // field names + + static final String NAME = "name"; + static final String NOTES = "notes"; + // reserved fields + static final String ICON = "icon"; + static final String PARENT = "parent"; + static final String FLAGS = "flags"; + static final String LOCATION_LAT = "locationLat"; + static final String LOCATION_LONG = "locationLong"; + static final String NOTIFICATIONS = "notifications"; + // end reserved fields + static final String CREATION_DATE = "creationDate"; + + /** Default values container */ + private static final ContentValues defaultValues = new ContentValues(); + + static { + defaultValues.put(NAME, ""); + defaultValues.put(NOTES, ""); + defaultValues.put(ICON, 0); + defaultValues.put(PARENT, 0); + defaultValues.put(FLAGS, 0); + defaultValues.put(LOCATION_LAT, 0); + defaultValues.put(LOCATION_LONG, 0); + defaultValues.put(NOTIFICATIONS, 0); + } + + @Override + public ContentValues getDefaultValues() { + return defaultValues; + } + + // --- database helper + + /** Database Helper manages creating new tables and updating old ones */ + static class TagModelDatabaseHelper extends SQLiteOpenHelper { + String tableName; + Context context; + + TagModelDatabaseHelper(Context context, String databaseName, String tableName) { + super(context, databaseName, null, VERSION); + this.tableName = tableName; + this.context = context; + } + + @Override + public synchronized void onCreate(SQLiteDatabase db) { + String sql = new StringBuilder(). + append("CREATE TABLE IF NOT EXISTS ").append(tableName).append(" ("). + append(AbstractController.KEY_ROWID).append(" integer primary key autoincrement, "). + append(NAME).append(" text unique,"). + append(NOTES).append(" text,"). + append(ICON).append(" integer,"). + append(PARENT).append(" integer,"). + append(FLAGS).append(" integer,"). + append(LOCATION_LAT).append(" integer,"). + append(LOCATION_LONG).append(" integer,"). + append(NOTIFICATIONS).append(" integer,"). + append(CREATION_DATE).append(" integer"). + append(");").toString(); + db.execSQL(sql); + } + + @Override + public synchronized void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) { + Log.w(getClass().getSimpleName(), "Upgrading database from version " + + oldVersion + " to " + newVersion + "."); + + switch(oldVersion) { + default: + // we don't know how to handle it... show an error + throw new RuntimeException("Tags: Unsupported migration from " + oldVersion + " to " + newVersion); + } + } + } + + // --- utility methods + + + + // --- identifier + + private TagIdentifier identifier = null; + + public TagIdentifier getTagIdentifier() { + return identifier; + } + + void setTagIdentifier(TagIdentifier identifier) { + this.identifier = identifier; + } + + // --- constructor pass-through + + AbstractTagModel() { + super(); + } + + /** Read identifier from database */ + AbstractTagModel(Cursor cursor) { + super(cursor); + + Integer id = retrieveInteger(AbstractController.KEY_ROWID); + setTagIdentifier(new TagIdentifier(id)); + } + + /** Get identifier from argument */ + AbstractTagModel(TagIdentifier identifier, Cursor cursor) { + super(cursor); + + setTagIdentifier(identifier); + } + + // --- getters and setters: expose them as you see fit + + protected String getName() { + return retrieveString(NAME); + } + + protected String getNotes() { + return retrieveString(NOTES); + } + + protected Date getCreationDate() { + return retrieveDate(CREATION_DATE); + } + + // --- setters + + protected void setName(String name) { + setValues.put(NAME, name.trim()); + } + + protected void setNotes(String notes) { + setValues.put(NOTES, notes); + } + + protected void setCreationDate(Date creationDate) { + putDate(setValues, CREATION_DATE, creationDate); + } + + // --- utility methods + + static void putDate(ContentValues cv, String fieldName, Date date) { + if(date == null) + cv.put(fieldName, (Long)null); + else + cv.put(fieldName, date.getTime()); + } +} diff --git a/tests/src/com/todoroo/astrid/legacy/data/tag/TagController.java b/tests/src/com/todoroo/astrid/legacy/data/tag/TagController.java new file mode 100644 index 000000000..0fd8c91ad --- /dev/null +++ b/tests/src/com/todoroo/astrid/legacy/data/tag/TagController.java @@ -0,0 +1,320 @@ +/* + * ASTRID: Android's Simple Task Recording Dashboard + * + * Copyright (c) 2009 Tim Su + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ +package com.todoroo.astrid.legacy.data.tag; + +import java.util.HashMap; +import java.util.HashSet; +import java.util.LinkedList; + +import android.content.ContentValues; +import android.content.Context; +import android.database.Cursor; +import android.database.SQLException; +import android.database.sqlite.SQLiteDatabase; + +import com.timsu.astrid.provider.TasksProvider; +import com.todoroo.astrid.legacy.data.AbstractController; +import com.todoroo.astrid.legacy.data.tag.AbstractTagModel.TagModelDatabaseHelper; +import com.todoroo.astrid.legacy.data.tag.TagToTaskMapping.TagToTaskMappingDatabaseHelper; +import com.todoroo.astrid.legacy.data.task.TaskIdentifier; +import com.todoroo.astrid.legacy.data.task.AbstractTaskModel.TaskModelDatabaseHelper; + +/** Controller for Tag-related operations */ +public class TagController extends AbstractController { + + private SQLiteDatabase tagDatabase, tagToTaskMapDatabase; + + // --- tag batch operations + + /** Get a list of all tags */ + public LinkedList getAllTags() + throws SQLException { + LinkedList list = new LinkedList(); + Cursor cursor = tagDatabase.query(TAG_TABLE_NAME, + TagModelForView.FIELD_LIST, null, null, null, null, null, null); + + try { + if(cursor.getCount() == 0) + return list; + do { + cursor.moveToNext(); + list.add(new TagModelForView(cursor)); + } while(!cursor.isLast()); + } finally { + cursor.close(); + } + + return list; + } + + // --- tag to task map batch operations + + /** Get a list of all tags as an id => tag map */ + public HashMap getAllTagsAsMap() throws SQLException { + HashMap map = new HashMap(); + for(TagModelForView tag : getAllTags()) + map.put(tag.getTagIdentifier(), tag); + return map; + } + + /** Get a list of tag identifiers for the given task */ + public LinkedList getTaskTags(TaskIdentifier + taskId) throws SQLException { + LinkedList list = new LinkedList(); + Cursor cursor = tagToTaskMapDatabase.query(TAG_TASK_MAP_NAME, + TagToTaskMapping.FIELD_LIST, TagToTaskMapping.TASK + " = ?", + new String[] { taskId.idAsString() }, null, null, null); + + try { + if(cursor.getCount() == 0) + return list; + do { + cursor.moveToNext(); + list.add(new TagToTaskMapping(cursor).getTag()); + } while(!cursor.isLast()); + } finally { + cursor.close(); + } + + return list; + } + + /** Get a list of task identifiers for the given tag. + * This searches for TAGGED tasks only. + * Use getUntaggedTasks() to get a list of UNTAGGED tasks **/ + public LinkedList getTaggedTasks(TagIdentifier tagId) + throws SQLException { + LinkedList list = new LinkedList(); + Cursor cursor = tagToTaskMapDatabase.query(TAG_TASK_MAP_NAME, + TagToTaskMapping.FIELD_LIST, TagToTaskMapping.TAG + " = ?", + new String[] { tagId.idAsString() }, null, null, null); + + try { + if(cursor.getCount() == 0) + return list; + do { + cursor.moveToNext(); + list.add(new TagToTaskMapping(cursor).getTask()); + } while(!cursor.isLast()); + } finally { + cursor.close(); + } + + return list; + } + + /** Returns a list of task identifiers in the provided set that are UNtagged. + * + * The calling SubActivity must provide the set of tasks, since + * TagController cannot access the appropriate instance of TaskController. + * + * The current implementation is not very efficient, because queries + * the TagToTask map once for each active task. + **/ + public LinkedList getUntaggedTasks() throws SQLException { + HashSet ids = new HashSet(); + + String[] tagMapColumns = new String[] { TagToTaskMapping.TASK }; + Cursor tagMapCursor = tagToTaskMapDatabase.query(TAG_TASK_MAP_NAME, + tagMapColumns, null, null, TagToTaskMapping.TASK, null, + TagToTaskMapping.TASK + " ASC"); + + SQLiteDatabase taskDatabase = new TaskModelDatabaseHelper(context, + TASK_TABLE_NAME, TASK_TABLE_NAME).getReadableDatabase(); + String[] taskColumns = new String[] { KEY_ROWID }; + Cursor taskCursor = taskDatabase.query(TASK_TABLE_NAME, taskColumns, + null, null, null, null, KEY_ROWID + " ASC"); + + LinkedList list = new LinkedList(); + try { + if(taskCursor.getCount() == 0) + return list; + + do { + taskCursor.moveToNext(); + ids.add(taskCursor.getLong(0)); + } while(!taskCursor.isLast()); + + if(tagMapCursor.getCount() > 0) { + do { + tagMapCursor.moveToNext(); + ids.remove(tagMapCursor.getLong(0)); + } while(!tagMapCursor.isLast()); + } + } finally { + taskCursor.close(); + tagMapCursor.close(); + taskDatabase.close(); + } + + for(Long id : ids) + list.add(new TaskIdentifier(id)); + return list; + } + + + // --- single tag operations + + public TagIdentifier createTag(String name) throws SQLException { + if(name == null) + throw new NullPointerException("Name can't be null"); + + TagModelForView newTag = new TagModelForView(name); + long row = tagDatabase.insertOrThrow(TAG_TABLE_NAME, AbstractTagModel.NAME, + newTag.getMergedValues()); + return new TagIdentifier(row); + } + + /** Creates or saves the given tag */ + public boolean saveTag(AbstractTagModel tag) throws SQLException { + boolean saveSucessful; + + if(tag.getTagIdentifier() == null) { + long newRow = tagDatabase.insert(TAG_TABLE_NAME, AbstractTagModel.NAME, + tag.getMergedValues()); + tag.setTagIdentifier(new TagIdentifier(newRow)); + + saveSucessful = newRow >= 0; + } else { + long id = tag.getTagIdentifier().getId(); + saveSucessful = tagDatabase.update(TAG_TABLE_NAME, tag.getSetValues(), + KEY_ROWID + "=" + id, null) > 0; + } + + return saveSucessful; + } + + /** Returns a TaskModelForView corresponding to the given Tag Name */ + public TagModelForView fetchTagFromName(String name) throws SQLException { + Cursor cursor = tagDatabase.query(true, TAG_TABLE_NAME, + TagModelForView.FIELD_LIST, + AbstractTagModel.NAME + " = ?", new String[] {name}, null, null, null, null); + + try { + if (cursor != null && cursor.getCount() > 0) { + cursor.moveToFirst(); + TagModelForView model = new TagModelForView(cursor); + return model; + } + return null; + } finally { + if(cursor != null) + cursor.close(); + } + } + + /** Returns a TaskModelForView corresponding to the given TagIdentifier */ + public TagModelForView fetchTagForView(TagIdentifier tagId) throws SQLException { + long id = tagId.getId(); + Cursor cursor = tagDatabase.query(true, TAG_TABLE_NAME, + TagModelForView.FIELD_LIST, + KEY_ROWID + "=" + id, null, null, null, null, null); + + try { + if (cursor != null) { + cursor.moveToFirst(); + TagModelForView model = new TagModelForView(cursor); + return model; + } + + throw new SQLException("Returned empty set!"); + } finally { + if(cursor != null) + cursor.close(); + } + } + + /** Deletes the tag and removes tag/task mappings */ + public boolean deleteTag( TagIdentifier tagId) + throws SQLException{ + if(tagToTaskMapDatabase.delete(TAG_TASK_MAP_NAME, + TagToTaskMapping.TAG + " = " + tagId.idAsString(), null) < 0) + return false; + + int res = tagDatabase.delete(TAG_TABLE_NAME, + KEY_ROWID + " = " + tagId.idAsString(), null); + + return res > 0; + } + + // --- single tag to task operations + + /** Remove the given tag from the task */ + public boolean removeTag(TaskIdentifier taskId, TagIdentifier tagId) + throws SQLException{ + + int res = tagToTaskMapDatabase.delete(TAG_TASK_MAP_NAME, + String.format("%s = ? AND %s = ?", + TagToTaskMapping.TAG, TagToTaskMapping.TASK), + new String[] { tagId.idAsString(), taskId.idAsString() }); + + // notify modification + TasksProvider.notifyDatabaseModification(); + + return res > 0; + } + + /** Add the given tag to the task */ + public boolean addTag(TaskIdentifier taskId, TagIdentifier tagId) + throws SQLException { + ContentValues values = new ContentValues(); + values.put(TagToTaskMapping.TAG, tagId.getId()); + values.put(TagToTaskMapping.TASK, taskId.getId()); + + long res = tagToTaskMapDatabase.insert(TAG_TASK_MAP_NAME, TagToTaskMapping.TAG, + values); + + return res >= 0; + } + + // --- boilerplate + + /** + * Constructor - takes the context to allow the database to be + * opened/created + */ + public TagController(Context context) { + this.context = context; + } + + /** + * Open the notes database. If it cannot be opened, try to create a new + * instance of the database. If it cannot be created, throw an exception to + * signal the failure + * + * @return this (self reference, allowing this to be chained in an + * initialization call) + * @throws SQLException if the database could be neither opened or created + */ + @Override + public synchronized void open() throws SQLException { + tagToTaskMapDatabase = new TagToTaskMappingDatabaseHelper(context, + TAG_TASK_MAP_NAME, TAG_TASK_MAP_NAME).getWritableDatabase(); + tagDatabase = new TagModelDatabaseHelper(context, + TAG_TABLE_NAME, TAG_TABLE_NAME).getWritableDatabase(); + } + + /** Closes database resource */ + @Override + public void close() { + tagDatabase.close(); + tagToTaskMapDatabase.close(); + } +} diff --git a/tests/src/com/todoroo/astrid/legacy/data/tag/TagIdentifier.java b/tests/src/com/todoroo/astrid/legacy/data/tag/TagIdentifier.java new file mode 100644 index 000000000..6ee5bbd54 --- /dev/null +++ b/tests/src/com/todoroo/astrid/legacy/data/tag/TagIdentifier.java @@ -0,0 +1,31 @@ +/* + * ASTRID: Android's Simple Task Recording Dashboard + * + * Copyright (c) 2009 Tim Su + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ +package com.todoroo.astrid.legacy.data.tag; + +import com.todoroo.astrid.legacy.data.Identifier; + + +public class TagIdentifier extends Identifier { + + public TagIdentifier(long id) { + super(id); + } + +} diff --git a/tests/src/com/todoroo/astrid/legacy/data/tag/TagModelForView.java b/tests/src/com/todoroo/astrid/legacy/data/tag/TagModelForView.java new file mode 100644 index 000000000..bd929aac2 --- /dev/null +++ b/tests/src/com/todoroo/astrid/legacy/data/tag/TagModelForView.java @@ -0,0 +1,102 @@ +/* + * ASTRID: Android's Simple Task Recording Dashboard + * + * Copyright (c) 2009 Tim Su + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ +package com.todoroo.astrid.legacy.data.tag; + +import android.database.Cursor; + +import com.todoroo.astrid.legacy.data.AbstractController; + + +/** Tag model for viewing purposes. Contains task name */ +public class TagModelForView extends AbstractTagModel { + + static String[] FIELD_LIST = new String[] { + AbstractController.KEY_ROWID, + NAME, + }; + + // negative number, should not conflict with database row #'s + public static final TagIdentifier UNTAGGED_IDENTIFIER = new TagIdentifier(Long.MIN_VALUE); + public static final String UNTAGGED_DEFAULT_NAME = "[untagged]"; + private static TagModelForView UNTAGGED_TASKS = new TagModelForView(UNTAGGED_DEFAULT_NAME); + + public static final String HIDDEN_FROM_MAIN_LIST_PREFIX = "_"; + + /** + * Returns a TagModelForView object to represent "Untagged" tasks, + * whose Identifier is defined by the static final UNTAGGED_IDENTIFIER. + * + * Pass in a string to show the "Untagged" name in the desired language. + * @param untaggedLabel + * @return + */ + public static TagModelForView getUntaggedModel(String untaggedLabel) { + UNTAGGED_TASKS = new TagModelForView(untaggedLabel); + UNTAGGED_TASKS.setTagIdentifier(UNTAGGED_IDENTIFIER); + return UNTAGGED_TASKS; + } + + /** + * Returns the default/last-used TagModelForView representing "Untagged" + * tasks. Set the localized name using getUntaggedModel(String...) + */ + public static TagModelForView getUntaggedModel() { + UNTAGGED_TASKS.setTagIdentifier(UNTAGGED_IDENTIFIER); + return UNTAGGED_TASKS; + } + + + // --- constructors + + /** Constructor for creating a new model */ + TagModelForView(String name) { + super(); + setName(name); + } + + /** Constructor for getting an existing model */ + TagModelForView(Cursor cursor) { + super(cursor); + getName(); + } + + // --- getters and setters + + @Override + public String getName() { + return super.getName(); + } + + @Override + public String toString() { + return getName(); + } + + public boolean shouldHideFromMainList() { + return getName().startsWith(HIDDEN_FROM_MAIN_LIST_PREFIX); + } + + public void toggleHideFromMainList() { + if(shouldHideFromMainList()) + setName(getName().substring(HIDDEN_FROM_MAIN_LIST_PREFIX.length())); + else + setName(HIDDEN_FROM_MAIN_LIST_PREFIX + getName()); + } +} diff --git a/tests/src/com/todoroo/astrid/legacy/data/tag/TagToTaskMapping.java b/tests/src/com/todoroo/astrid/legacy/data/tag/TagToTaskMapping.java new file mode 100644 index 000000000..c8e898654 --- /dev/null +++ b/tests/src/com/todoroo/astrid/legacy/data/tag/TagToTaskMapping.java @@ -0,0 +1,130 @@ +/* + * ASTRID: Android's Simple Task Recording Dashboard + * + * Copyright (c) 2009 Tim Su + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ +package com.todoroo.astrid.legacy.data.tag; + +import android.content.ContentValues; +import android.content.Context; +import android.database.Cursor; +import android.database.sqlite.SQLiteDatabase; +import android.database.sqlite.SQLiteOpenHelper; +import android.util.Log; + +import com.todoroo.astrid.legacy.data.AbstractController; +import com.todoroo.astrid.legacy.data.AbstractModel; +import com.todoroo.astrid.legacy.data.task.TaskIdentifier; + + +/** A single tag on a task */ +public class TagToTaskMapping extends AbstractModel { + + /** Version number of this model */ + static final int VERSION = 2; + + // field names + + static final String TASK = "task"; + static final String TAG = "tag"; + + /** Default values container */ + private static final ContentValues defaultValues = new ContentValues(); + + @Override + public ContentValues getDefaultValues() { + return defaultValues; + } + + static String[] FIELD_LIST = new String[] { + AbstractController.KEY_ROWID, + TASK, + TAG, + }; + + // --- database helper + + /** Database Helper manages creating new tables and updating old ones */ + static class TagToTaskMappingDatabaseHelper extends SQLiteOpenHelper { + String tableName; + Context context; + + TagToTaskMappingDatabaseHelper(Context context, String databaseName, String tableName) { + super(context, databaseName, null, VERSION); + this.tableName = tableName; + this.context = context; + } + + @Override + public synchronized void onCreate(SQLiteDatabase db) { + String sql = new StringBuilder(). + append("CREATE TABLE IF NOT EXISTS ").append(tableName).append(" ("). + append(AbstractController.KEY_ROWID).append(" integer primary key autoincrement, "). + append(TASK).append(" integer not null,"). + append(TAG).append(" integer not null,"). + append("unique (").append(TASK).append(",").append(TAG).append(")"). + append(");").toString(); + db.execSQL(sql); + } + + @Override + public synchronized void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) { + Log.w(getClass().getSimpleName(), "Upgrading database from version " + + oldVersion + " to " + newVersion + "."); + + switch(oldVersion) { + default: + throw new RuntimeException("Tag: Unsupported migration from " + oldVersion + " to " + newVersion); + } + } + } + + + // --- constructor pass-through + + TagToTaskMapping(TaskIdentifier task, TagIdentifier tag) { + super(); + setTask(task); + setTag(tag); + } + + TagToTaskMapping(Cursor cursor) { + super(cursor); + } + + // --- getters and setters: expose them as you see fit + + public boolean isNew() { + return getCursor() == null; + } + + public TaskIdentifier getTask() { + return new TaskIdentifier(retrieveInteger(TASK)); + } + + public TagIdentifier getTag() { + return new TagIdentifier(retrieveInteger(TAG)); + } + + private void setTask(TaskIdentifier task) { + setValues.put(TASK, task.getId()); + } + + private void setTag(TagIdentifier tag) { + setValues.put(TAG, tag.getId()); + } +} diff --git a/tests/src/com/todoroo/astrid/legacy/data/task/AbstractTaskModel.java b/tests/src/com/todoroo/astrid/legacy/data/task/AbstractTaskModel.java new file mode 100644 index 000000000..290a00f55 --- /dev/null +++ b/tests/src/com/todoroo/astrid/legacy/data/task/AbstractTaskModel.java @@ -0,0 +1,606 @@ +/* + * ASTRID: Android's Simple Task Recording Dashboard + * + * Copyright (c) 2009 Tim Su + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ +package com.todoroo.astrid.legacy.data.task; + +import java.util.Date; + +import android.content.ContentValues; +import android.content.Context; +import android.database.Cursor; +import android.database.sqlite.SQLiteDatabase; +import android.database.sqlite.SQLiteOpenHelper; +import android.util.Log; + +import com.todoroo.astrid.legacy.data.AbstractController; +import com.todoroo.astrid.legacy.data.AbstractModel; +import com.todoroo.astrid.legacy.data.enums.Importance; +import com.todoroo.astrid.legacy.data.enums.RepeatInterval; + + +/** Abstract model of a task. Subclasses implement the getters and setters + * they are interested in. + * + * @author timsu + * + */ +public abstract class AbstractTaskModel extends AbstractModel { + + /** Version number of this model */ + static final int VERSION = 8; + + public static final int COMPLETE_PERCENTAGE = 100; + + // field names + + public static final String NAME = "name"; + public static final String NOTES = "notes"; + public static final String PROGRESS_PERCENTAGE = "progressPercentage"; + public static final String IMPORTANCE = "importance"; + public static final String ESTIMATED_SECONDS = "estimatedSeconds"; + public static final String ELAPSED_SECONDS = "elapsedSeconds"; + public static final String TIMER_START = "timerStart"; + public static final String DEFINITE_DUE_DATE = "definiteDueDate"; + public static final String PREFERRED_DUE_DATE = "preferredDueDate"; + public static final String HIDDEN_UNTIL = "hiddenUntil"; + public static final String POSTPONE_COUNT = "postponeCount"; + public static final String NOTIFICATIONS = "notifications"; + public static final String NOTIFICATION_FLAGS = "notificationFlags"; + public static final String LAST_NOTIFIED = "lastNotified"; + public static final String REPEAT = "repeat"; + public static final String CREATION_DATE = "creationDate"; + public static final String COMPLETION_DATE = "completionDate"; + public static final String CALENDAR_URI = "calendarUri"; + public static final String FLAGS = "flags"; + + // reserved fields --- + public static final String BLOCKING_ON = "blockingOn"; + + // notification flags + public static final int NOTIFY_BEFORE_DEADLINE = 1 << 0; + public static final int NOTIFY_AT_DEADLINE = 1 << 1; + public static final int NOTIFY_AFTER_DEADLINE = 1 << 2; + public static final int NOTIFY_NONSTOP = 1 << 3; + + // other flags + public static final int FLAG_SYNC_ON_COMPLETE = 1 << 0; + + /** Number of bits to shift repeat value by */ + public static final int REPEAT_VALUE_OFFSET = 3; + + /** Default values container */ + private static final ContentValues defaultValues = new ContentValues(); + + static { + defaultValues.put(NAME, ""); + defaultValues.put(NOTES, ""); + defaultValues.put(PROGRESS_PERCENTAGE, 0); + defaultValues.put(IMPORTANCE, Importance.DEFAULT.ordinal()); + defaultValues.put(ESTIMATED_SECONDS, 0); + defaultValues.put(ELAPSED_SECONDS, 0); + defaultValues.put(TIMER_START, 0); + defaultValues.put(DEFINITE_DUE_DATE, 0); + defaultValues.put(PREFERRED_DUE_DATE, 0); + defaultValues.put(HIDDEN_UNTIL, 0); + defaultValues.put(BLOCKING_ON, 0); + defaultValues.put(POSTPONE_COUNT, 0); + defaultValues.put(NOTIFICATIONS, 0); + defaultValues.put(NOTIFICATION_FLAGS, NOTIFY_AT_DEADLINE); + defaultValues.put(LAST_NOTIFIED, 0); + defaultValues.put(REPEAT, 0); + defaultValues.put(COMPLETION_DATE, 0); + defaultValues.put(CALENDAR_URI, (String)null); + defaultValues.put(FLAGS, 0); + } + + // --- database helper + + /** Database Helper manages creating new tables and updating old ones */ + public static class TaskModelDatabaseHelper extends SQLiteOpenHelper { + String tableName; + Context context; + + public TaskModelDatabaseHelper(Context context, String databaseName, String tableName) { + super(context, databaseName, null, VERSION); + this.tableName = tableName; + this.context = context; + } + + @Override + public synchronized void onCreate(SQLiteDatabase db) { + String sql = new StringBuilder(). + append("CREATE TABLE IF NOT EXISTS ").append(tableName).append(" ("). + append(AbstractController.KEY_ROWID).append(" integer primary key autoincrement, "). + append(NAME).append(" text not null,"). + append(NOTES).append(" text not null,"). + append(PROGRESS_PERCENTAGE).append(" integer not null,"). + append(IMPORTANCE).append(" integer not null,"). + append(ESTIMATED_SECONDS).append(" integer,"). + append(ELAPSED_SECONDS).append(" integer,"). + append(TIMER_START).append(" integer,"). + append(DEFINITE_DUE_DATE).append(" integer,"). + append(PREFERRED_DUE_DATE).append(" integer,"). + append(HIDDEN_UNTIL).append(" integer,"). + append(BLOCKING_ON).append(" integer,"). + append(POSTPONE_COUNT).append(" integer,"). + append(NOTIFICATIONS).append(" integer,"). + append(NOTIFICATION_FLAGS).append(" integer,"). + append(LAST_NOTIFIED).append(" integer,"). + append(REPEAT).append(" integer,"). + append(FLAGS).append(" integer,"). + append(CREATION_DATE).append(" integer,"). + append(COMPLETION_DATE).append(" integer,"). + append(CALENDAR_URI).append(" text"). + append(");").toString(); + db.execSQL(sql); + } + + @Override + @SuppressWarnings("fallthrough") + public synchronized void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) { + Log.w(getClass().getSimpleName(), "Upgrading database from version " + + oldVersion + " to " + newVersion + "."); + String sql; + + // note: we execute sql statements in their own try block to be more + // graceful if an upgrade dies halfway or something + switch(oldVersion) { + case 1: + sql = new StringBuilder().append("ALTER TABLE "). + append(tableName).append(" ADD COLUMN "). + append(LAST_NOTIFIED).append(" integer").toString(); + try { + db.execSQL(sql); + } catch (Exception e) { + Log.e("astrid", "Error updating table!", e); + } + sql = new StringBuilder().append("ALTER TABLE "). + append(tableName).append(" ADD COLUMN "). + append(NOTIFICATION_FLAGS).append(" integer").toString(); + try { + db.execSQL(sql); + } catch (Exception e) { + Log.e("astrid", "Error updating table!", e); + } + + case 2: + sql = new StringBuilder().append("ALTER TABLE "). + append(tableName).append(" ADD COLUMN "). + append(REPEAT).append(" integer").toString(); + try { + db.execSQL(sql); + } catch (Exception e) { + Log.e("astrid", "Error updating table!", e); + } + + case 3: + sql = new StringBuilder().append("ALTER TABLE "). + append(tableName).append(" ADD COLUMN "). + append(CALENDAR_URI).append(" text").toString(); + try { + db.execSQL(sql); + } catch (Exception e) { + Log.e("astrid", "Error updating table!", e); + } + + case 4: + sql = new StringBuilder().append("ALTER TABLE "). + append(tableName).append(" ADD COLUMN "). + append(POSTPONE_COUNT).append(" integer").toString(); + try { + db.execSQL(sql); + } catch (Exception e) { + Log.e("astrid", "Error updating table!", e); + } + + case 5: + case 6: + // apparently some people didn't get the flags column + // from version 5 to version 6, so we try again + + sql = new StringBuilder().append("ALTER TABLE "). + append(tableName).append(" ADD COLUMN "). + append(FLAGS).append(" integer").toString(); + try { + db.execSQL(sql); + } catch (Exception e) { + Log.e("astrid", "Error updating table!", e); + } + + case 7: + // not a real change, but make sure that columns that are null + // are converted into zeros, which was my previous assumption + + for(String column : new String[] { + ESTIMATED_SECONDS, + ELAPSED_SECONDS, + TIMER_START, + DEFINITE_DUE_DATE, + PREFERRED_DUE_DATE, + HIDDEN_UNTIL, + POSTPONE_COUNT, + LAST_NOTIFIED, + REPEAT, + CREATION_DATE, + COMPLETION_DATE }) { + sql = String.format("UPDATE %s SET %s = 0 WHERE %s ISNULL", + tableName, column, column); + try { + db.execSQL(sql); + } catch (Exception e) { + Log.e("astrid", "Error updating table!", e); + } + } + + // --- break point + + break; + + default: + // we don't know how to handle it... show an error + throw new RuntimeException("Tasks: Unsupported migration from " + oldVersion + " to " + newVersion); + } + } + } + + // --- utility methods + + /** Gets task color. Requires definiteDueDate and importance */ + protected int getTaskColorResource(Context context) { + if(getDefiniteDueDate() != null && getDefiniteDueDate().getTime() < + System.currentTimeMillis()) { + return 0; + } + return 1; + } + + /** Checks whether task is done. Requires progressPercentage */ + protected boolean isTaskCompleted() { + return getProgressPercentage() >= COMPLETE_PERCENTAGE; + } + + /** Stops the timer & increments elapsed time. Requires timerStart and + * elapsedSeconds */ + protected void stopTimerAndUpdateElapsedTime() { + if(getTimerStart() == null) + return; + + long start = getTimerStart().getTime(); + setTimerStart(null); + long secondsElapsed = (System.currentTimeMillis() - start)/1000; + setElapsedSeconds((int) (getElapsedSeconds() + secondsElapsed)); + } + + protected void prefetchData(String[] fields) { + for(String field : fields) { + if(field.equals(NAME)) + getName(); + else if(field.equals(NOTES)) + getNotes(); + else if(field.equals(PROGRESS_PERCENTAGE)) + getProgressPercentage(); + else if(field.equals(IMPORTANCE)) + getImportance(); + else if(field.equals(ESTIMATED_SECONDS)) + getEstimatedSeconds(); + else if(field.equals(ELAPSED_SECONDS)) + getElapsedSeconds(); + else if(field.equals(TIMER_START)) + getTimerStart(); + else if(field.equals(DEFINITE_DUE_DATE)) + getDefiniteDueDate(); + else if(field.equals(PREFERRED_DUE_DATE)) + getPreferredDueDate(); + else if(field.equals(HIDDEN_UNTIL)) + getHiddenUntil(); + else if(field.equals(BLOCKING_ON)) + getBlockingOn(); + else if(field.equals(POSTPONE_COUNT)) + getPostponeCount(); + else if(field.equals(NOTIFICATIONS)) + getNotificationIntervalSeconds(); + else if(field.equals(CREATION_DATE)) + getCreationDate(); + else if(field.equals(COMPLETION_DATE)) + getCompletionDate(); + else if(field.equals(NOTIFICATION_FLAGS)) + getNotificationFlags(); + else if(field.equals(LAST_NOTIFIED)) + getLastNotificationDate(); + else if(field.equals(REPEAT)) + getRepeat(); + else if(field.equals(FLAGS)) + getFlags(); + } + } + + // --- helper classes + + public static class RepeatInfo { + private RepeatInterval interval; + private int value; + + public RepeatInfo(RepeatInterval repeatInterval, int value) { + this.interval = repeatInterval; + this.value = value; + } + + public Date shiftDate(Date input) { + Date newDate = (Date)input.clone(); + interval.offsetDateBy(newDate, value); + return newDate; + } + + public RepeatInterval getInterval() { + return interval; + } + + public int getValue() { + return value; + } + + } + + // --- task identifier + + private TaskIdentifier identifier = null; + + public TaskIdentifier getTaskIdentifier() { + return identifier; + } + + void setTaskIdentifier(TaskIdentifier identifier) { + this.identifier = identifier; + } + + // --- constructors and abstract methods + + AbstractTaskModel() { + super(); + } + + /** Read identifier from database */ + AbstractTaskModel(Cursor cursor) { + super(cursor); + + Integer id = retrieveInteger(AbstractController.KEY_ROWID); + setTaskIdentifier(new TaskIdentifier(id)); + } + + /** Get identifier from argument */ + AbstractTaskModel(TaskIdentifier identifier, Cursor cursor) { + super(cursor); + + setTaskIdentifier(identifier); + } + + @Override + public ContentValues getDefaultValues() { + return defaultValues; + } + + // --- getters and setters: expose them as you see fit + + protected String getName() { + return retrieveString(NAME); + } + + protected String getNotes() { + return retrieveString(NOTES); + } + + protected int getProgressPercentage() { + return retrieveInteger(PROGRESS_PERCENTAGE); + } + + protected Importance getImportance() { + Integer value = retrieveInteger(IMPORTANCE); + if(value == null) + return null; + return Importance.values()[value]; + } + + protected Integer getEstimatedSeconds() { + return retrieveInteger(ESTIMATED_SECONDS); + } + + protected Integer getElapsedSeconds() { + return retrieveInteger(ELAPSED_SECONDS); + } + + protected Date getTimerStart() { + return retrieveDate(TIMER_START); + } + + protected Date getDefiniteDueDate() { + return retrieveDate(DEFINITE_DUE_DATE); + } + + protected Date getPreferredDueDate() { + return retrieveDate(PREFERRED_DUE_DATE); + } + + protected Date getHiddenUntil() { + return retrieveDate(HIDDEN_UNTIL); + } + + protected boolean isHidden() { + if(getHiddenUntil() == null) + return false; + return getHiddenUntil().getTime() > System.currentTimeMillis(); + } + + protected Date getCreationDate() { + return retrieveDate(CREATION_DATE); + } + + protected Date getCompletionDate() { + return retrieveDate(COMPLETION_DATE); + } + + protected TaskIdentifier getBlockingOn() { + Long value = retrieveLong(BLOCKING_ON); + if(value == null) + return null; + return new TaskIdentifier(value); + } + + protected Integer getPostponeCount() { + return retrieveInteger(POSTPONE_COUNT); + } + + protected Integer getNotificationIntervalSeconds() { + return retrieveInteger(NOTIFICATIONS); + } + + protected int getNotificationFlags() { + return retrieveInteger(NOTIFICATION_FLAGS); + } + + protected Date getLastNotificationDate() { + return retrieveDate(LAST_NOTIFIED); + } + + protected RepeatInfo getRepeat() { + int repeat = retrieveInteger(REPEAT); + if(repeat == 0) + return null; + int value = repeat >> REPEAT_VALUE_OFFSET; + RepeatInterval interval = RepeatInterval.values() + [repeat - (value << REPEAT_VALUE_OFFSET)]; + + return new RepeatInfo(interval, value); + } + + protected String getCalendarUri() { + String uri = retrieveString(CALENDAR_URI); + if(uri != null && uri.length() == 0) + return null; + else + return uri; + } + + protected int getFlags() { + return retrieveInteger(FLAGS); + } + + // --- setters + + protected void setName(String name) { + putIfChangedFromDatabase(NAME, name); + } + + protected void setNotes(String notes) { + putIfChangedFromDatabase(NOTES, notes); + } + + protected void setProgressPercentage(int progressPercentage) { + putIfChangedFromDatabase(PROGRESS_PERCENTAGE, progressPercentage); + + if(getProgressPercentage() != progressPercentage && + progressPercentage == COMPLETE_PERCENTAGE) + setCompletionDate(new Date()); + } + + protected void setImportance(Importance importance) { + putIfChangedFromDatabase(IMPORTANCE, importance.ordinal()); + } + + protected void setEstimatedSeconds(Integer estimatedSeconds) { + putIfChangedFromDatabase(ESTIMATED_SECONDS, estimatedSeconds); + } + + protected void setElapsedSeconds(int elapsedSeconds) { + putIfChangedFromDatabase(ELAPSED_SECONDS, elapsedSeconds); + } + + protected void setTimerStart(Date timerStart) { + putDate(TIMER_START, timerStart); + } + + protected void setDefiniteDueDate(Date definiteDueDate) { + putDate(DEFINITE_DUE_DATE, definiteDueDate); + } + + protected void setPreferredDueDate(Date preferredDueDate) { + putDate(PREFERRED_DUE_DATE, preferredDueDate); + } + + protected void setHiddenUntil(Date hiddenUntil) { + putDate(HIDDEN_UNTIL, hiddenUntil); + } + + protected void setBlockingOn(TaskIdentifier blockingOn) { + if(blockingOn == null || blockingOn.equals(getTaskIdentifier())) + putIfChangedFromDatabase(BLOCKING_ON, (Integer)null); + else + putIfChangedFromDatabase(BLOCKING_ON, blockingOn.getId()); + } + + protected void setPostponeCount(int postponeCount) { + putIfChangedFromDatabase(POSTPONE_COUNT, postponeCount); + } + + protected void setCreationDate(Date creationDate) { + putDate(CREATION_DATE, creationDate); + } + + protected void setCompletionDate(Date completionDate) { + putDate(COMPLETION_DATE, completionDate); + } + + protected void setNotificationIntervalSeconds(Integer intervalInSeconds) { + putIfChangedFromDatabase(NOTIFICATIONS, intervalInSeconds); + } + + protected void setNotificationFlags(int flags) { + putIfChangedFromDatabase(NOTIFICATION_FLAGS, flags); + } + + protected void setLastNotificationTime(Date date) { + putDate(LAST_NOTIFIED, date); + } + + protected void setRepeat(RepeatInfo repeatInfo) { + int repeat; + if(repeatInfo == null) + repeat = 0; + else + repeat = (repeatInfo.value << REPEAT_VALUE_OFFSET) + + repeatInfo.interval.ordinal(); + putIfChangedFromDatabase(REPEAT, repeat); + } + + protected void setCalendarUri(String uri) { + putIfChangedFromDatabase(CALENDAR_URI, uri); + } + + protected void setFlags(int flags) { + putIfChangedFromDatabase(FLAGS, flags); + } + + // --- utility methods + + protected void putDate(String fieldName, Date date) { + if(date == null) + putIfChangedFromDatabase(fieldName, 0); + else + putIfChangedFromDatabase(fieldName, date.getTime()); + } +} diff --git a/tests/src/com/todoroo/astrid/legacy/data/task/TaskController.java b/tests/src/com/todoroo/astrid/legacy/data/task/TaskController.java new file mode 100644 index 000000000..35124d566 --- /dev/null +++ b/tests/src/com/todoroo/astrid/legacy/data/task/TaskController.java @@ -0,0 +1,556 @@ +/* + * ASTRID: Android's Simple Task Recording Dashboard + * + * Copyright (c) 2009 Tim Su + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ +package com.todoroo.astrid.legacy.data.task; + +import java.util.ArrayList; +import java.util.Date; +import java.util.HashSet; +import java.util.List; + +import android.app.Activity; +import android.content.ContentResolver; +import android.content.ContentValues; +import android.content.Context; +import android.database.Cursor; +import android.database.SQLException; +import android.database.sqlite.SQLiteDatabase; +import android.database.sqlite.SQLiteOpenHelper; +import android.net.Uri; +import android.util.Log; + +import com.todoroo.astrid.legacy.data.AbstractController; +import com.todoroo.astrid.legacy.data.sync.SyncDataController; +import com.todoroo.astrid.legacy.data.task.AbstractTaskModel.RepeatInfo; +import com.todoroo.astrid.legacy.data.task.AbstractTaskModel.TaskModelDatabaseHelper; + +/** + * Controller for task-related operations + * + * @author timsu + * + */ +public class TaskController extends AbstractController { + + private SQLiteDatabase database; + + // --- task list operations + + /** Return a list of all active tasks with notifications */ + public HashSet getTasksWithNotifications() { + HashSet list = new HashSet(); + Cursor cursor = database.query(TASK_TABLE_NAME, TaskModelForNotify.FIELD_LIST, + String.format("%s < %d AND (%s != 0 OR %s != 0)", + AbstractTaskModel.PROGRESS_PERCENTAGE, + AbstractTaskModel.COMPLETE_PERCENTAGE, + AbstractTaskModel.NOTIFICATIONS, + AbstractTaskModel.NOTIFICATION_FLAGS), null, null, null, null, null); + + try { + if(cursor.getCount() == 0) + return list; + do { + cursor.moveToNext(); + list.add(new TaskModelForNotify(cursor)); + } while(!cursor.isLast()); + + return list; + } finally { + cursor.close(); + } + } + + /** Return a list of all active tasks with deadlines */ + public ArrayList getTasksWithDeadlines() { + ArrayList list = new ArrayList(); + Cursor cursor = database.query(TASK_TABLE_NAME, TaskModelForNotify.FIELD_LIST, + String.format("%s < %d AND (%s != 0 OR %s != 0)", + AbstractTaskModel.PROGRESS_PERCENTAGE, + AbstractTaskModel.COMPLETE_PERCENTAGE, + AbstractTaskModel.DEFINITE_DUE_DATE, + AbstractTaskModel.PREFERRED_DUE_DATE), null, null, null, null, null); + + try { + if(cursor.getCount() == 0) + return list; + + do { + cursor.moveToNext(); + list.add(new TaskModelForNotify(cursor)); + } while(!cursor.isLast()); + + return list; + } finally { + cursor.close(); + } + } + + /** Return a list of all of the tasks with progress < COMPLETE_PERCENTAGE */ + public Cursor getActiveTaskListCursor() { + return database.query(TASK_TABLE_NAME, TaskModelForList.FIELD_LIST, + AbstractTaskModel.PROGRESS_PERCENTAGE + " < " + + AbstractTaskModel.COMPLETE_PERCENTAGE, null, null, null, + null, null); + } + + /** Return a list of all tasks */ + public Cursor getAllTaskListCursor() { + return database.query(TASK_TABLE_NAME, TaskModelForList.FIELD_LIST, + null, null, null, null, null, null); + } + + /** Return a list of all tasks */ + public Cursor getBackupTaskListCursor() { + return database.query(TASK_TABLE_NAME, TaskModelForXml.FIELD_LIST, + AbstractTaskModel.PROGRESS_PERCENTAGE + " < " + + AbstractTaskModel.COMPLETE_PERCENTAGE, null, null, null, + null, null); + } + + /** Delete all completed tasks with date < older than date */ + public int deleteCompletedTasksOlderThan(Date olderThanDate) { + return database.delete(TASK_TABLE_NAME, String.format("`%s` >= '%d' AND `%s` <= '%d'", + AbstractTaskModel.PROGRESS_PERCENTAGE, AbstractTaskModel.COMPLETE_PERCENTAGE, + AbstractTaskModel.COMPLETION_DATE, olderThanDate.getTime()), null); + } + + /** Create a list of tasks from the db cursor given */ + public ArrayList createTaskListFromCursor(Cursor cursor) { + ArrayList list = new ArrayList(); + + if(cursor.getCount() == 0) + return list; + + do { + cursor.moveToNext(); + list.add(new TaskModelForList(cursor)); + } while(!cursor.isLast()); + + return list; + } + + /** Helper method to take a cursor pointing to a list of id's and generate + * a hashset */ + private HashSet createTaskIdentifierSet(Cursor cursor) { + HashSet list = new HashSet(); + try { + if(cursor.getCount() == 0) + return list; + + do { + cursor.moveToNext(); + list.add(new TaskIdentifier(cursor.getInt( + cursor.getColumnIndexOrThrow(KEY_ROWID)))); + } while(!cursor.isLast()); + + return list; + } finally { + cursor.close(); + } + } + + /** Get identifiers for all tasks */ + public HashSet getAllTaskIdentifiers() { + Cursor cursor = database.query(TASK_TABLE_NAME, new String[] { KEY_ROWID }, + null, null, null, null, null, null); + return createTaskIdentifierSet(cursor); + } + + /** Get identifiers for all non-completed tasks */ + public HashSet getActiveTaskIdentifiers() { + Cursor cursor = database.query(TASK_TABLE_NAME, new String[] { KEY_ROWID }, + AbstractTaskModel.PROGRESS_PERCENTAGE + " < " + + AbstractTaskModel.COMPLETE_PERCENTAGE, null, null, null, null, null); + return createTaskIdentifierSet(cursor); + } + + /** Get identifiers for all non-completed, non-hidden tasks */ + public HashSet getActiveVisibleTaskIdentifiers() { + Cursor cursor = database.query(TASK_TABLE_NAME, new String[] { KEY_ROWID }, + AbstractTaskModel.PROGRESS_PERCENTAGE + " < " + + AbstractTaskModel.COMPLETE_PERCENTAGE + " AND (" + + AbstractTaskModel.HIDDEN_UNTIL + " ISNULL OR " + AbstractTaskModel.HIDDEN_UNTIL + " < " + + System.currentTimeMillis() + ")", null, null, null, null, null); + return createTaskIdentifierSet(cursor); + } + + + /** Create a weighted list of tasks from the db cursor given */ + public Cursor getTaskListCursorById(List idList) { + + StringBuilder where = new StringBuilder(); + for(int i = 0; i < idList.size(); i++) { + where.append(KEY_ROWID); + where.append("="); + where.append(idList.get(i).idAsString()); + if(i < idList.size()-1) + where.append(" OR "); + } + + // hack for empty arrays + if(idList.size() == 0) + where.append("0"); + + return database.query(true, TASK_TABLE_NAME, + TaskModelForList.FIELD_LIST, where.toString(), null, null, + null, null, null); + } + + // --- single task operations + + /** Delete the given task */ + public boolean deleteTask(TaskIdentifier taskId) { + if(taskId == null) + throw new UnsupportedOperationException("Cannot delete uncreated task!"); + long id = taskId.getId(); + cleanupTask(taskId, false); + + return database.delete(TASK_TABLE_NAME, KEY_ROWID + "=" + id, null) > 0; + } + + /** Saves the given task to the database. Returns true on success. + * + * @param duringSync set to true when save is part of a synchronize + */ + public boolean saveTask(AbstractTaskModel task, boolean duringSync) { + boolean saveSucessful; + + if(task.getTaskIdentifier() == null) { + long newRow = database.insert(TASK_TABLE_NAME, AbstractTaskModel.NAME, + task.getMergedValues()); + task.setTaskIdentifier(new TaskIdentifier(newRow)); + + saveSucessful = newRow >= 0; + } else { + long id = task.getTaskIdentifier().getId(); + ContentValues values = task.getSetValues(); + + if(values.size() == 0) // nothing changed + return true; + + onTaskSave(task, values, duringSync); + + saveSucessful = database.update(TASK_TABLE_NAME, values, + KEY_ROWID + "=" + id, null) > 0; + + // task was completed + if(values.containsKey(AbstractTaskModel.PROGRESS_PERCENTAGE) && + values.getAsInteger(AbstractTaskModel.PROGRESS_PERCENTAGE) + == AbstractTaskModel.COMPLETE_PERCENTAGE) { + onTaskCompleted(task, values, duringSync); + } + + SyncDataController.taskUpdated(context, task); + } + + return saveSucessful; + } + + /** + * Called when the task is saved. Perform some processing on the task. + * + * @param task + * @param values + */ + private void onTaskSave(AbstractTaskModel task, ContentValues values, boolean duringSync) { + // save task completed date + if(values.containsKey(AbstractTaskModel.PROGRESS_PERCENTAGE) && + values.getAsInteger(AbstractTaskModel.PROGRESS_PERCENTAGE) + == AbstractTaskModel.COMPLETE_PERCENTAGE) { + values.put(AbstractTaskModel.COMPLETION_DATE, System.currentTimeMillis()); + } + } + + + /** + * Called when this task is set to completed. + * + * @param task task to process + * @param values mutable map of values to save + */ + private void onTaskCompleted(AbstractTaskModel task, ContentValues values, boolean duringSync) { + Cursor cursor = fetchTaskCursor(task.getTaskIdentifier(), + TaskModelForHandlers.FIELD_LIST); + TaskModelForHandlers model = new TaskModelForHandlers(cursor, values); + + // handle repeat + RepeatInfo repeatInfo = model.getRepeat(); + if(repeatInfo != null) { + model.repeatTaskBy(context, this, repeatInfo); + database.update(TASK_TABLE_NAME, values, KEY_ROWID + "=" + + task.getTaskIdentifier().getId(), null); + } + + cursor.close(); + cleanupTask(task.getTaskIdentifier(), repeatInfo != null); + } + + /** Clean up state from a task. Called when deleting or completing it */ + private void cleanupTask(TaskIdentifier taskId, boolean isRepeating) { + // delete calendar event if not repeating + if(!isRepeating) { + try { + Cursor cursor = fetchTaskCursor(taskId, new String[] { + AbstractTaskModel.CALENDAR_URI }); + cursor.moveToFirst(); + String uri = cursor.getString(0); + cursor.close(); + if(uri != null && uri.length() > 0) { + ContentResolver cr = context.getContentResolver(); + cr.delete(Uri.parse(uri), null, null); + ContentValues values = new ContentValues(); + values.put(AbstractTaskModel.CALENDAR_URI, (String)null); + database.update(TASK_TABLE_NAME, values, KEY_ROWID + "=" + + taskId.getId(), null); + } + } catch (Exception e) { + Log.e("astrid", "Error deleting calendar event", e); + } + } + } + + /** Set last notification date */ + public boolean setLastNotificationTime(TaskIdentifier taskId, Date date) { + ContentValues values = new ContentValues(); + values.put(AbstractTaskModel.LAST_NOTIFIED, date.getTime()); + return database.update(TASK_TABLE_NAME, values, + KEY_ROWID + "=" + taskId.getId(), null) > 0; + } + + // --- fetching different models + + /** Creates a new task and returns the task identifier */ + public TaskModelForEdit createNewTaskForEdit() { + TaskModelForEdit task = new TaskModelForEdit(); + task.setTaskIdentifier(null); + + return task; + } + + /** Returns a TaskModelForEdit corresponding to the given TaskIdentifier */ + public TaskModelForEdit fetchTaskForEdit(Activity activity, TaskIdentifier + taskId) throws SQLException { + Cursor cursor = fetchTaskCursor(taskId, TaskModelForEdit.FIELD_LIST); + activity.startManagingCursor(cursor); + TaskModelForEdit model = new TaskModelForEdit(taskId, cursor); + return model; + } + + /** Returns a TaskModelForList corresponding to the given TaskIdentifier */ + public TaskModelForList fetchTaskForList(TaskIdentifier taskId) throws SQLException { + Cursor cursor = fetchTaskCursor(taskId, TaskModelForList.FIELD_LIST); + TaskModelForList model = new TaskModelForList(cursor); + cursor.close(); + return model; + } + + /** Returns a TaskModelForXml corresponding to the given TaskIdentifier */ + public TaskModelForXml fetchTaskForXml(TaskIdentifier taskId) throws SQLException { + Cursor cursor = fetchTaskCursor(taskId, TaskModelForXml.FIELD_LIST); + TaskModelForXml model = new TaskModelForXml(cursor); + cursor.close(); + return model; + } + + /* Attempts to return a TaskModelForXml for the given name and creation date */ + public TaskModelForXml fetchTaskForXml(String name, Date creationDate) { + Cursor cursor; + try { + cursor = fetchTaskCursor(name, creationDate.getTime(), + TaskModelForXml.FIELD_LIST); + } catch (SQLException e) { + return null; + } + if (cursor == null || cursor.getCount() == 0) { + return null; + } + TaskModelForXml model = new TaskModelForXml(cursor); + cursor.close(); + return model; + } + + /** Returns a TaskModelForReminder corresponding to the given TaskIdentifier */ + public TaskModelForReminder fetchTaskForReminder(TaskIdentifier taskId) throws SQLException { + Cursor cursor = fetchTaskCursor(taskId, TaskModelForReminder.FIELD_LIST); + TaskModelForReminder model = new TaskModelForReminder(cursor); + cursor.close(); + return model; + } + + /** Returns a TaskModelForSync corresponding to the given TaskIdentifier */ + public TaskModelForSync fetchTaskForSync(TaskIdentifier taskId) throws SQLException { + Cursor cursor = fetchTaskCursor(taskId, TaskModelForSync.FIELD_LIST); + TaskModelForSync model = new TaskModelForSync(cursor); + cursor.close(); + return model; + } + + /** Returns a TaskModelForView by name */ + public TaskModelForSync searchForTaskForSync(String name) throws SQLException { + Cursor cursor = database.query(true, TASK_TABLE_NAME, TaskModelForSync.FIELD_LIST, + AbstractTaskModel.NAME + " = ? AND " + + AbstractTaskModel.PROGRESS_PERCENTAGE + " < "+ + AbstractTaskModel.COMPLETE_PERCENTAGE, + new String[] { name }, null, null, null, null); + try { + if (cursor == null || cursor.getCount() == 0) + return null; + cursor.moveToFirst(); + TaskModelForSync model = new TaskModelForSync(cursor); + return model; + } finally { + if(cursor != null) + cursor.close(); + } + } + + /** Returns a TaskModelForView corresponding to the given TaskIdentifier */ + public TaskModelForNotify fetchTaskForNotify(TaskIdentifier taskId) throws SQLException { + Cursor cursor = fetchTaskCursor(taskId, TaskModelForNotify.FIELD_LIST); + TaskModelForNotify model = new TaskModelForNotify(cursor); + cursor.close(); + return model; + } + + /** Moves cursor to the task. + * Don't forget to close the cursor when you're done. */ + private Cursor fetchTaskCursor(TaskIdentifier taskId, String[] fieldList) { + long id = taskId.getId(); + Cursor cursor = database.query(true, TASK_TABLE_NAME, fieldList, + KEY_ROWID + "=" + id, null, null, null, null, null); + if (cursor == null) + throw new SQLException("Returned empty set!"); + + cursor.moveToFirst(); + return cursor; + } + + /** Returns null if unsuccessful, otherwise moves cursor to the task. + * Don't forget to close the cursor when you're done. */ + private Cursor fetchTaskCursor(String name, long creationDate, String[] fieldList) { + // truncate millis + final String where = AbstractTaskModel.NAME + " = ? AND " + + AbstractTaskModel.CREATION_DATE + " LIKE ?"; + + String approximateCreationDate = (creationDate / 1000) + "%"; + Cursor cursor = database.query(true, TASK_TABLE_NAME, fieldList, + where, new String[] {name, approximateCreationDate}, null, null, null, null); + if (cursor == null) + throw new SQLException("Returned empty set!"); + + if (cursor.moveToFirst()) { + return cursor; + } + cursor.close(); + return null; + } + // --- methods supporting individual features + + /** Returns a TaskModelForView corresponding to the given TaskIdentifier */ + public int fetchTaskPostponeCount(TaskIdentifier taskId) throws SQLException { + Cursor cursor = fetchTaskCursor(taskId, new String[] {AbstractTaskModel.POSTPONE_COUNT}); + try { + if (cursor == null || cursor.getCount() == 0) + return 0; + cursor.moveToFirst(); + return cursor.getInt(0); + } catch (Exception e) { + return 0; + } finally { + if(cursor != null) + cursor.close(); + } + } + + public ArrayList getTasksForWidget(String limit) { + + Cursor cursor = database.query(TASK_TABLE_NAME, TaskModelForWidget.FIELD_LIST, + AbstractTaskModel.PROGRESS_PERCENTAGE + " < " + + AbstractTaskModel.COMPLETE_PERCENTAGE + " AND (" + + AbstractTaskModel.HIDDEN_UNTIL + " ISNULL OR " + AbstractTaskModel.HIDDEN_UNTIL + " < " + + System.currentTimeMillis() + ")", null, null, null, + AbstractTaskModel.IMPORTANCE + " * " + (5 * 24 * 3600 * 1000L) + + " + CASE WHEN MAX(pdd, ddd) = 0 THEN " + + (System.currentTimeMillis() + (7 * 24 * 3600 * 1000L)) + + " ELSE (CASE WHEN pdd = 0 THEN ddd ELSE pdd END) END ASC", limit); + + try { + ArrayList list = new ArrayList(); + for(cursor.moveToFirst(); !cursor.isAfterLast(); cursor.moveToNext()) + list.add(new TaskModelForWidget(cursor)); + return list; + } finally { + cursor.close(); + } + } + + public ArrayList getTasksForProvider(String limit) { + + Cursor cursor = database.query(TASK_TABLE_NAME, TaskModelForWidget.FIELD_LIST, + AbstractTaskModel.PROGRESS_PERCENTAGE + " < " + + AbstractTaskModel.COMPLETE_PERCENTAGE + " AND (" + + AbstractTaskModel.HIDDEN_UNTIL + " ISNULL OR " + AbstractTaskModel.HIDDEN_UNTIL + " < " + + System.currentTimeMillis() + ")", null, null, null, + AbstractTaskModel.IMPORTANCE + " * " + (5 * 24 * 3600 * 1000L) + + " + CASE WHEN MAX(pdd, ddd) = 0 THEN " + + (System.currentTimeMillis() + (7 * 24 * 3600 * 1000L)) + + " ELSE (CASE WHEN pdd = 0 THEN ddd ELSE pdd END) END ASC", limit); + + try { + ArrayList list = new ArrayList(); + for(cursor.moveToFirst(); !cursor.isAfterLast(); cursor.moveToNext()) + list.add(new TaskModelForProvider(cursor)); + return list; + } finally { + cursor.close(); + } + } + + // --- boilerplate + + /** + * Constructor - takes the context to allow the database to be + * opened/created + */ + public TaskController(Context activity) { + this.context = activity; + } + + /** + * Open the notes database. If it cannot be opened, try to create a new + * instance of the database. If it cannot be created, throw an exception to + * signal the failure + * + * @return this (self reference, allowing this to be chained in an + * initialization call) + * @throws SQLException if the database could be neither opened or created + */ + @Override + public synchronized void open() throws SQLException { + SQLiteOpenHelper databaseHelper = new TaskModelDatabaseHelper( + context, TASK_TABLE_NAME, TASK_TABLE_NAME); + database = databaseHelper.getWritableDatabase(); + } + + /** Closes database resource */ + @Override + public void close() { + database.close(); + } +} diff --git a/tests/src/com/todoroo/astrid/legacy/data/task/TaskIdentifier.java b/tests/src/com/todoroo/astrid/legacy/data/task/TaskIdentifier.java new file mode 100644 index 000000000..882441c78 --- /dev/null +++ b/tests/src/com/todoroo/astrid/legacy/data/task/TaskIdentifier.java @@ -0,0 +1,30 @@ +/* + * ASTRID: Android's Simple Task Recording Dashboard + * + * Copyright (c) 2009 Tim Su + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ +package com.todoroo.astrid.legacy.data.task; + +import com.todoroo.astrid.legacy.data.Identifier; + + +public class TaskIdentifier extends Identifier { + + public TaskIdentifier(long id) { + super(id); + } +} diff --git a/tests/src/com/todoroo/astrid/legacy/data/task/TaskModelForEdit.java b/tests/src/com/todoroo/astrid/legacy/data/task/TaskModelForEdit.java new file mode 100644 index 000000000..bf983d8da --- /dev/null +++ b/tests/src/com/todoroo/astrid/legacy/data/task/TaskModelForEdit.java @@ -0,0 +1,208 @@ +/* + * ASTRID: Android's Simple Task Recording Dashboard + * + * Copyright (c) 2009 Tim Su + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ +package com.todoroo.astrid.legacy.data.task; + +import java.util.Date; + +import android.database.Cursor; + +import com.todoroo.astrid.legacy.data.enums.Importance; + + + +/** Fields that you would want to edit in the TaskModel */ +public class TaskModelForEdit extends AbstractTaskModel { + + static String[] FIELD_LIST = new String[] { + NAME, + IMPORTANCE, + ESTIMATED_SECONDS, + ELAPSED_SECONDS, + DEFINITE_DUE_DATE, + PREFERRED_DUE_DATE, + HIDDEN_UNTIL, + BLOCKING_ON, + NOTIFICATIONS, + NOTIFICATION_FLAGS, + LAST_NOTIFIED, + PROGRESS_PERCENTAGE, + NOTES, + REPEAT, + CALENDAR_URI, + }; + + // --- constructors + + public TaskModelForEdit() { + super(); + setCreationDate(new Date()); + } + + public TaskModelForEdit(TaskIdentifier identifier, Cursor cursor) { + super(identifier, cursor); + } + + // --- getters and setters + + @Override + public boolean isTaskCompleted() { + return super.isTaskCompleted(); + } + + @Override + public Integer getNotificationIntervalSeconds() { + return super.getNotificationIntervalSeconds(); + } + + @Override + public void setNotificationIntervalSeconds(Integer intervalInSeconds) { + super.setNotificationIntervalSeconds(intervalInSeconds); + } + + @Override + public Date getDefiniteDueDate() { + return super.getDefiniteDueDate(); + } + + @Override + public Integer getEstimatedSeconds() { + return super.getEstimatedSeconds(); + } + + @Override + public Integer getElapsedSeconds() { + return super.getElapsedSeconds(); + } + + @Override + public Date getHiddenUntil() { + return super.getHiddenUntil(); + } + + @Override + public Importance getImportance() { + return super.getImportance(); + } + + @Override + public String getName() { + return super.getName(); + } + + @Override + public String getNotes() { + return super.getNotes(); + } + + @Override + public Date getPreferredDueDate() { + return super.getPreferredDueDate(); + } + + @Override + public int getProgressPercentage() { + return super.getProgressPercentage(); + } + + @Override + public TaskIdentifier getBlockingOn() { + return super.getBlockingOn(); + } + + @Override + public int getNotificationFlags() { + return super.getNotificationFlags(); + } + + @Override + public Date getLastNotificationDate() { + return super.getLastNotificationDate(); + } + + @Override + public RepeatInfo getRepeat() { + return super.getRepeat(); + } + + @Override + public String getCalendarUri() { + return super.getCalendarUri(); + } + + @Override + public void setDefiniteDueDate(Date definiteDueDate) { + super.setDefiniteDueDate(definiteDueDate); + } + + @Override + public void setEstimatedSeconds(Integer estimatedSeconds) { + super.setEstimatedSeconds(estimatedSeconds); + } + + @Override + public void setElapsedSeconds(int elapsedSeconds) { + super.setElapsedSeconds(elapsedSeconds); + } + + @Override + public void setHiddenUntil(Date hiddenUntil) { + super.setHiddenUntil(hiddenUntil); + } + + @Override + public void setImportance(Importance importance) { + super.setImportance(importance); + } + + @Override + public void setName(String name) { + super.setName(name); + } + + @Override + public void setNotes(String notes) { + super.setNotes(notes); + } + + @Override + public void setPreferredDueDate(Date preferredDueDate) { + super.setPreferredDueDate(preferredDueDate); + } + + @Override + public void setBlockingOn(TaskIdentifier blockingOn) { + super.setBlockingOn(blockingOn); + } + + @Override + public void setNotificationFlags(int flags) { + super.setNotificationFlags(flags); + } + + @Override + public void setRepeat(RepeatInfo taskRepeat) { + super.setRepeat(taskRepeat); + } + + @Override + public void setCalendarUri(String uri) { + super.setCalendarUri(uri); + } +} diff --git a/tests/src/com/todoroo/astrid/legacy/data/task/TaskModelForHandlers.java b/tests/src/com/todoroo/astrid/legacy/data/task/TaskModelForHandlers.java new file mode 100644 index 000000000..7e4e8cc43 --- /dev/null +++ b/tests/src/com/todoroo/astrid/legacy/data/task/TaskModelForHandlers.java @@ -0,0 +1,168 @@ +/* + * ASTRID: Android's Simple Task Recording Dashboard + * + * Copyright (c) 2009 Tim Su + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ +package com.todoroo.astrid.legacy.data.task; + +import java.util.Date; +import java.util.List; + +import android.content.ContentValues; +import android.content.Context; +import android.database.Cursor; + +import com.todoroo.astrid.legacy.data.AbstractController; +import com.todoroo.astrid.legacy.data.alerts.AlertController; + + + +/** Fields that you would want to read or edit in the onTaskSave and onTaskComplete + * event handlers */ +public class TaskModelForHandlers extends AbstractTaskModel { + + static String[] FIELD_LIST = new String[] { + AbstractController.KEY_ROWID, + REPEAT, + DEFINITE_DUE_DATE, + PREFERRED_DUE_DATE, + HIDDEN_UNTIL, + PROGRESS_PERCENTAGE, + ESTIMATED_SECONDS, + LAST_NOTIFIED, + NOTIFICATIONS, + NOTIFICATION_FLAGS, + FLAGS, + }; + + /** + * This method updates the task to reflect a new repeat iteration. It moves + * back the due dates and updates other task properties accordingly. + * + * @param context + * @param taskController + * @param repeatInfo + */ + public void repeatTaskBy(Context context, TaskController taskController, + RepeatInfo repeatInfo) { + + // move dates back + if(getDefiniteDueDate() != null) + setDefiniteDueDate(repeatInfo.shiftDate(getDefiniteDueDate())); + if(getHiddenUntil() != null) + setHiddenUntil(repeatInfo.shiftDate(getHiddenUntil())); + if(getPreferredDueDate() != null) + setPreferredDueDate(repeatInfo.shiftDate(getPreferredDueDate())); + setProgressPercentage(0); + + // set elapsed time to 0... yes, we're losing data + setElapsedSeconds(0); + + // if no deadlines set, create one (so users don't get confused) + if(getDefiniteDueDate() == null && getPreferredDueDate() == null) + setPreferredDueDate(repeatInfo.shiftDate(new Date())); + + // shift fixed alerts + AlertController alertController = new AlertController(context); + alertController.open(); + List alerts = alertController.getTaskAlerts(getTaskIdentifier()); + alertController.removeAlerts(getTaskIdentifier()); + for(int i = 0; i < alerts.size(); i++) { + Date newAlert = repeatInfo.shiftDate(alerts.get(i)); + alertController.addAlert(getTaskIdentifier(), newAlert); + alerts.set(i, newAlert); + } + + // reset periodic alerts + setLastNotificationTime(null); + alertController.close(); + } + + // --- constructors + + public TaskModelForHandlers(Cursor cursor, ContentValues setValues) { + super(cursor); + this.setValues = setValues; + } + + // --- getters and setters + + @Override + public RepeatInfo getRepeat() { + return super.getRepeat(); + } + + @Override + public Integer getNotificationIntervalSeconds() { + return super.getNotificationIntervalSeconds(); + } + + @Override + public boolean isTaskCompleted() { + return super.isTaskCompleted(); + } + + @Override + public Date getDefiniteDueDate() { + return super.getDefiniteDueDate(); + } + + @Override + public Integer getEstimatedSeconds() { + return super.getEstimatedSeconds(); + } + + @Override + public Date getHiddenUntil() { + return super.getHiddenUntil(); + } + + @Override + public Date getPreferredDueDate() { + return super.getPreferredDueDate(); + } + @Override + public int getNotificationFlags() { + return super.getNotificationFlags(); + } + + @Override + public Date getLastNotificationDate() { + return super.getLastNotificationDate(); + } + + @Override + public int getFlags() { + return super.getFlags(); + } + + @Override + public void setDefiniteDueDate(Date definiteDueDate) { + super.setDefiniteDueDate(definiteDueDate); + } + + @Override + public void setPreferredDueDate(Date preferredDueDate) { + super.setPreferredDueDate(preferredDueDate); + } + + @Override + public void setHiddenUntil(Date hiddenUntil) { + super.setHiddenUntil(hiddenUntil); + } + +} diff --git a/tests/src/com/todoroo/astrid/legacy/data/task/TaskModelForList.java b/tests/src/com/todoroo/astrid/legacy/data/task/TaskModelForList.java new file mode 100644 index 000000000..ceb9dd1d4 --- /dev/null +++ b/tests/src/com/todoroo/astrid/legacy/data/task/TaskModelForList.java @@ -0,0 +1,267 @@ +/* + * ASTRID: Android's Simple Task Recording Dashboard + * + * Copyright (c) 2009 Tim Su + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ +package com.todoroo.astrid.legacy.data.task; + +import java.util.Date; +import java.util.HashMap; + +import android.content.Context; +import android.database.Cursor; + +import com.todoroo.astrid.legacy.data.AbstractController; +import com.todoroo.astrid.legacy.data.enums.Importance; + + + +/** Fields that you would want to edit in the TaskModel */ +public class TaskModelForList extends AbstractTaskModel { + + static String[] FIELD_LIST = new String[] { + AbstractController.KEY_ROWID, + NAME, + IMPORTANCE, + ELAPSED_SECONDS, + ESTIMATED_SECONDS, + TIMER_START, + DEFINITE_DUE_DATE, + PREFERRED_DUE_DATE, + NOTIFICATIONS, + PROGRESS_PERCENTAGE, + COMPLETION_DATE, + CREATION_DATE, + HIDDEN_UNTIL, + NOTES, + REPEAT, + FLAGS, + }; + + // pre-load the cache for our column keys + static { + HashMap indexCache = new HashMap(); + columnIndexCache.put(TaskModelForList.class, indexCache); + for(int i = 0; i < FIELD_LIST.length; i++) + indexCache.put(FIELD_LIST[i], i); + } + + /** Get the weighted score for this task. Smaller is more important */ + public int getTaskWeight() { + int weight = 0; + + // bubble tasks with timers to the top + if(getTimerStart() != null) + weight -= 10000; + + // importance + weight += getImportance().ordinal() * 80; + + // looming absolute deadline + if(getDefiniteDueDate() != null) { + int hoursLeft = (int) ((getDefiniteDueDate().getTime() - + System.currentTimeMillis())/1000/3600); + if(hoursLeft < 5*24) + weight += (hoursLeft - 5*24); + weight -= 20; + } + + // looming preferred deadline + if(getPreferredDueDate() != null) { + int hoursLeft = (int) ((getPreferredDueDate().getTime() - + System.currentTimeMillis())/1000/3600); + if(hoursLeft < 5*24) + weight += (hoursLeft - 5*24)/2; + weight -= 10; + } + + // bubble completed tasks to the bottom + if(isTaskCompleted()) { + if(getCompletionDate() == null) + weight += 1e6; + else + weight = (int)Math.max(10000 + (System.currentTimeMillis() - + getCompletionDate().getTime()) / 1000, 10000); + return weight; + } + + return weight; + } + + @Override + public boolean isHidden() { + return super.isHidden(); + } + + /** map of cached display labels */ + private HashMap displayLabels = new HashMap(); + + public String getCachedLabel(int key) { + return displayLabels.get(key); + } + public void putCachedLabel(int key, String value) { + displayLabels.put(key, value); + } + public void clearCache() { + displayLabels.clear(); + } + + // --- constructors + + public TaskModelForList(Cursor cursor) { + super(cursor); + + prefetchData(FIELD_LIST); + } + + // --- exposed getters and setters + + @Override + public boolean isTaskCompleted() { + return super.isTaskCompleted(); + } + + @Override + public int getTaskColorResource(Context context) { + return super.getTaskColorResource(context); + } + + @Override + public Integer getElapsedSeconds() { + return super.getElapsedSeconds(); + } + + public static int getCompletedPercentage() { + return COMPLETE_PERCENTAGE; + } + + @Override + public Date getDefiniteDueDate() { + return super.getDefiniteDueDate(); + } + + @Override + public Integer getEstimatedSeconds() { + return super.getEstimatedSeconds(); + } + + @Override + public Date getHiddenUntil() { + return super.getHiddenUntil(); + } + + @Override + public Importance getImportance() { + return super.getImportance(); + } + + @Override + public String getName() { + return super.getName(); + } + + @Override + public Date getPreferredDueDate() { + return super.getPreferredDueDate(); + } + + @Override + public int getProgressPercentage() { + return super.getProgressPercentage(); + } + + @Override + public Date getTimerStart() { + return super.getTimerStart(); + } + + @Override + public Date getCompletionDate() { + return super.getCompletionDate(); + } + + @Override + public String getNotes() { + return super.getNotes(); + } + + @Override + public Integer getNotificationIntervalSeconds() { + return super.getNotificationIntervalSeconds(); + } + + @Override + public RepeatInfo getRepeat() { + return super.getRepeat(); + } + + @Override + public Date getCreationDate() { + return super.getCreationDate(); + } + + @Override + public int getFlags() { + return super.getFlags(); + } + + // --- setters + + @Override + public void setProgressPercentage(int progressPercentage) { + super.setProgressPercentage(progressPercentage); + } + + @Override + public void setTimerStart(Date timerStart) { + super.setTimerStart(timerStart); + } + + @Override + public void stopTimerAndUpdateElapsedTime() { + super.stopTimerAndUpdateElapsedTime(); + } + + public static String getNameField() { + return NAME; + } + + @Override + public void setPreferredDueDate(Date preferredDueDate) { + super.setPreferredDueDate(preferredDueDate); + } + + @Override + public void setDefiniteDueDate(Date definiteDueDate) { + super.setDefiniteDueDate(definiteDueDate); + } + + @Override + public void setImportance(Importance importance) { + super.setImportance(importance); + } + + @Override + public void setHiddenUntil(Date hiddenUntil) { + super.setHiddenUntil(hiddenUntil); + } + + @Override + public void setPostponeCount(int postponeCount) { + super.setPostponeCount(postponeCount); + } +} diff --git a/tests/src/com/todoroo/astrid/legacy/data/task/TaskModelForNotify.java b/tests/src/com/todoroo/astrid/legacy/data/task/TaskModelForNotify.java new file mode 100644 index 000000000..d148765b5 --- /dev/null +++ b/tests/src/com/todoroo/astrid/legacy/data/task/TaskModelForNotify.java @@ -0,0 +1,101 @@ +/* + * ASTRID: Android's Simple Task Recording Dashboard + * + * Copyright (c) 2009 Tim Su + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ +package com.todoroo.astrid.legacy.data.task; + +import java.util.Date; + +import android.database.Cursor; + +import com.todoroo.astrid.legacy.data.AbstractController; + + + +/** Fields that you would want to see in the TaskView activity */ +public class TaskModelForNotify extends AbstractTaskModel { + + static String[] FIELD_LIST = new String[] { + AbstractController.KEY_ROWID, + ESTIMATED_SECONDS, + NOTIFICATIONS, + NOTIFICATION_FLAGS, + LAST_NOTIFIED, + HIDDEN_UNTIL, + PROGRESS_PERCENTAGE, + DEFINITE_DUE_DATE, + PREFERRED_DUE_DATE, + }; + + // --- constructors + + public TaskModelForNotify(Cursor cursor) { + super(cursor); + + prefetchData(FIELD_LIST); + } + + // --- getters + + @Override + public Integer getEstimatedSeconds() { + return super.getEstimatedSeconds(); + } + + @Override + public boolean isTaskCompleted() { + return super.isTaskCompleted(); + } + + @Override + public Integer getNotificationIntervalSeconds() { + return super.getNotificationIntervalSeconds(); + } + + @Override + public Date getHiddenUntil() { + return super.getHiddenUntil(); + } + + @Override + public Date getDefiniteDueDate() { + return super.getDefiniteDueDate(); + } + + @Override + public Date getPreferredDueDate() { + return super.getPreferredDueDate(); + } + + @Override + public int getNotificationFlags() { + return super.getNotificationFlags(); + } + + @Override + public Date getLastNotificationDate() { + return super.getLastNotificationDate(); + } + + // --- setters + + @Override + public void setLastNotificationTime(Date date) { + super.setLastNotificationTime(date); + } +} diff --git a/tests/src/com/todoroo/astrid/legacy/data/task/TaskModelForProvider.java b/tests/src/com/todoroo/astrid/legacy/data/task/TaskModelForProvider.java new file mode 100644 index 000000000..ab548771d --- /dev/null +++ b/tests/src/com/todoroo/astrid/legacy/data/task/TaskModelForProvider.java @@ -0,0 +1,73 @@ +/* + * ASTRID: Android's Simple Task Recording Dashboard + * + * Copyright (c) 2009 Francois DESLANDES + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ +package com.todoroo.astrid.legacy.data.task; + +import java.util.Date; + +import android.database.Cursor; + +import com.todoroo.astrid.legacy.data.AbstractController; +import com.todoroo.astrid.legacy.data.enums.Importance; + + + +/** Fields that you would want to see in the TaskView activity */ +public class TaskModelForProvider extends AbstractTaskModel { + + static String[] FIELD_LIST = new String[] { + AbstractController.KEY_ROWID, + NAME, + IMPORTANCE, + PREFERRED_DUE_DATE, + DEFINITE_DUE_DATE, + "COALESCE(" + PREFERRED_DUE_DATE + ", 0) as pdd", + "COALESCE(" + DEFINITE_DUE_DATE + ", 0) as ddd" + }; + + // --- constructors + + public TaskModelForProvider(Cursor cursor) { + super(cursor); + + prefetchData(FIELD_LIST); + } + + // --- getters + + @Override + public String getName() { + return super.getName(); + } + + @Override + public Importance getImportance() { + return super.getImportance(); + } + + @Override + public Date getPreferredDueDate() { + return super.getPreferredDueDate(); + } + + @Override + public Date getDefiniteDueDate() { + return super.getDefiniteDueDate(); + } +} diff --git a/tests/src/com/todoroo/astrid/legacy/data/task/TaskModelForReminder.java b/tests/src/com/todoroo/astrid/legacy/data/task/TaskModelForReminder.java new file mode 100644 index 000000000..d0b737cb1 --- /dev/null +++ b/tests/src/com/todoroo/astrid/legacy/data/task/TaskModelForReminder.java @@ -0,0 +1,83 @@ +/* + * ASTRID: Android's Simple Task Recording Dashboard + * + * Copyright (c) 2009 Tim Su + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ +package com.todoroo.astrid.legacy.data.task; + +import java.util.Date; + +import android.database.Cursor; + +import com.todoroo.astrid.legacy.data.AbstractController; + + + +/** Fields that you would want to see in the TaskView activity */ +public class TaskModelForReminder extends AbstractTaskModel { + + static String[] FIELD_LIST = new String[] { + AbstractController.KEY_ROWID, + NAME, + NOTIFICATION_FLAGS, + HIDDEN_UNTIL, + TIMER_START, + PROGRESS_PERCENTAGE, + }; + + // --- constructors + + public TaskModelForReminder(Cursor cursor) { + super(cursor); + + prefetchData(FIELD_LIST); + } + + // --- getters + + @Override + public String getName() { + return super.getName(); + } + + @Override + public Date getTimerStart() { + return super.getTimerStart(); + } + + @Override + public boolean isTaskCompleted() { + return super.isTaskCompleted(); + } + + @Override + public Date getHiddenUntil() { + return super.getHiddenUntil(); + } + + @Override + public int getNotificationFlags() { + return super.getNotificationFlags(); + } + + // --- setters + + @Override + public void setLastNotificationTime(Date date) { + super.setLastNotificationTime(date); + } +} diff --git a/tests/src/com/todoroo/astrid/legacy/data/task/TaskModelForSync.java b/tests/src/com/todoroo/astrid/legacy/data/task/TaskModelForSync.java new file mode 100644 index 000000000..9e97c7958 --- /dev/null +++ b/tests/src/com/todoroo/astrid/legacy/data/task/TaskModelForSync.java @@ -0,0 +1,236 @@ +/* + * ASTRID: Android's Simple Task Recording Dashboard + * + * Copyright (c) 2009 Tim Su + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ +package com.todoroo.astrid.legacy.data.task; + +import java.util.Date; + +import android.database.Cursor; + +import com.todoroo.astrid.legacy.data.AbstractController; +import com.todoroo.astrid.legacy.data.enums.Importance; + + + +/** Fields that you would want to synchronize in the TaskModel */ +public class TaskModelForSync extends AbstractTaskModel { + + static String[] FIELD_LIST = new String[] { + AbstractController.KEY_ROWID, + NAME, + IMPORTANCE, + ESTIMATED_SECONDS, + ELAPSED_SECONDS, + DEFINITE_DUE_DATE, + PREFERRED_DUE_DATE, + HIDDEN_UNTIL, + BLOCKING_ON, + PROGRESS_PERCENTAGE, + CREATION_DATE, + COMPLETION_DATE, + NOTES, + REPEAT, + LAST_NOTIFIED, + NOTIFICATIONS, + NOTIFICATION_FLAGS, + FLAGS, + }; + + // --- constructors + + public TaskModelForSync() { + super(); + setCreationDate(new Date()); + } + + public TaskModelForSync(Cursor cursor) { + super(cursor); + prefetchData(FIELD_LIST); + } + + // --- getters and setters + + @Override + public boolean isTaskCompleted() { + return super.isTaskCompleted(); + } + + @Override + public Date getDefiniteDueDate() { + return super.getDefiniteDueDate(); + } + + @Override + public Integer getEstimatedSeconds() { + return super.getEstimatedSeconds(); + } + + @Override + public int getProgressPercentage() { + return super.getProgressPercentage(); + } + + @Override + public Date getCreationDate() { + return super.getCreationDate(); + } + + @Override + public Date getCompletionDate() { + return super.getCompletionDate(); + } + + @Override + public Integer getElapsedSeconds() { + return super.getElapsedSeconds(); + } + + @Override + public Date getHiddenUntil() { + return super.getHiddenUntil(); + } + + @Override + public Importance getImportance() { + return super.getImportance(); + } + + @Override + public String getName() { + return super.getName(); + } + + @Override + public String getNotes() { + return super.getNotes(); + } + + @Override + public Date getPreferredDueDate() { + return super.getPreferredDueDate(); + } + + @Override + public TaskIdentifier getBlockingOn() { + return super.getBlockingOn(); + } + + @Override + public RepeatInfo getRepeat() { + return super.getRepeat(); + } + + @Override + public Integer getNotificationIntervalSeconds() { + return super.getNotificationIntervalSeconds(); + } + + @Override + public int getNotificationFlags() { + return super.getNotificationFlags(); + } + + @Override + public Date getLastNotificationDate() { + return super.getLastNotificationDate(); + } + + @Override + public int getFlags() { + return super.getFlags(); + } + + // --- setters + + @Override + public void setDefiniteDueDate(Date definiteDueDate) { + super.setDefiniteDueDate(definiteDueDate); + } + + @Override + public void setEstimatedSeconds(Integer estimatedSeconds) { + super.setEstimatedSeconds(estimatedSeconds); + } + + @Override + public void setElapsedSeconds(int elapsedSeconds) { + super.setElapsedSeconds(elapsedSeconds); + } + + @Override + public void setHiddenUntil(Date hiddenUntil) { + super.setHiddenUntil(hiddenUntil); + } + + @Override + public void setImportance(Importance importance) { + super.setImportance(importance); + } + + @Override + public void setName(String name) { + super.setName(name); + } + + @Override + public void setNotes(String notes) { + super.setNotes(notes); + } + + @Override + public void setPreferredDueDate(Date preferredDueDate) { + super.setPreferredDueDate(preferredDueDate); + } + + @Override + public void setBlockingOn(TaskIdentifier blockingOn) { + super.setBlockingOn(blockingOn); + } + + @Override + public void setRepeat(RepeatInfo taskRepeat) { + super.setRepeat(taskRepeat); + } + + @Override + public void setCompletionDate(Date completionDate) { + super.setCompletionDate(completionDate); + } + + @Override + public void setCreationDate(Date creationDate) { + super.setCreationDate(creationDate); + } + + @Override + public void setProgressPercentage(int progressPercentage) { + super.setProgressPercentage(progressPercentage); + } + + @Override + public void setNotificationIntervalSeconds(Integer intervalInSeconds) { + super.setNotificationIntervalSeconds(intervalInSeconds); + } + + @Override + public void setFlags(int flags) { + super.setFlags(flags); + } +} + diff --git a/tests/src/com/todoroo/astrid/legacy/data/task/TaskModelForWidget.java b/tests/src/com/todoroo/astrid/legacy/data/task/TaskModelForWidget.java new file mode 100644 index 000000000..aa2b70d76 --- /dev/null +++ b/tests/src/com/todoroo/astrid/legacy/data/task/TaskModelForWidget.java @@ -0,0 +1,73 @@ +/* + * ASTRID: Android's Simple Task Recording Dashboard + * + * Copyright (c) 2009 Tim Su + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ +package com.todoroo.astrid.legacy.data.task; + +import java.util.Date; + +import android.database.Cursor; + +import com.todoroo.astrid.legacy.data.AbstractController; +import com.todoroo.astrid.legacy.data.enums.Importance; + + + +/** Fields that you would want to see in the TaskView activity */ +public class TaskModelForWidget extends AbstractTaskModel { + + static String[] FIELD_LIST = new String[] { + AbstractController.KEY_ROWID, + NAME, + IMPORTANCE, + PREFERRED_DUE_DATE, + DEFINITE_DUE_DATE, + "COALESCE(" + PREFERRED_DUE_DATE + ", 0) as pdd", + "COALESCE(" + DEFINITE_DUE_DATE + ", 0) as ddd" + }; + + // --- constructors + + public TaskModelForWidget(Cursor cursor) { + super(cursor); + + prefetchData(FIELD_LIST); + } + + // --- getters + + @Override + public String getName() { + return super.getName(); + } + + @Override + public Importance getImportance() { + return super.getImportance(); + } + + @Override + public Date getPreferredDueDate() { + return super.getPreferredDueDate(); + } + + @Override + public Date getDefiniteDueDate() { + return super.getDefiniteDueDate(); + } +} diff --git a/tests/src/com/todoroo/astrid/legacy/data/task/TaskModelForXml.java b/tests/src/com/todoroo/astrid/legacy/data/task/TaskModelForXml.java new file mode 100644 index 000000000..0daba58d4 --- /dev/null +++ b/tests/src/com/todoroo/astrid/legacy/data/task/TaskModelForXml.java @@ -0,0 +1,218 @@ +package com.todoroo.astrid.legacy.data.task; + +import java.util.Date; +import java.util.HashMap; + +import android.database.Cursor; +import android.util.Log; + +import com.timsu.astrid.R; +import com.timsu.astrid.utilities.DateUtilities; +import com.todoroo.astrid.legacy.data.AbstractController; +import com.todoroo.astrid.legacy.data.enums.Importance; +import com.todoroo.astrid.legacy.data.enums.RepeatInterval; + +public class TaskModelForXml extends AbstractTaskModel { + + static String[] FIELD_LIST = new String[] { + AbstractController.KEY_ROWID, + NAME, + IMPORTANCE, + ELAPSED_SECONDS, + ESTIMATED_SECONDS, + TIMER_START, + DEFINITE_DUE_DATE, + PREFERRED_DUE_DATE, + NOTIFICATIONS, + PROGRESS_PERCENTAGE, + COMPLETION_DATE, + CREATION_DATE, + HIDDEN_UNTIL, + NOTES, + REPEAT, + FLAGS, + POSTPONE_COUNT, + BLOCKING_ON, + LAST_NOTIFIED, + NOTIFICATION_FLAGS, + CALENDAR_URI, + }; + private HashMap taskAttributesMap; + public static final String REPEAT_VALUE = "repeat_value"; + public static final String REPEAT_INTERVAL = "repeat_interval"; + + + private RepeatInterval repeatInterval = null; + private Integer repeatValue = null; + + // --- constructors + + public TaskModelForXml() { + super(); + setCreationDate(new Date()); + taskAttributesMap = new HashMap(FIELD_LIST.length); + } + + public TaskModelForXml(Cursor cursor) { + super(cursor); + prefetchData(FIELD_LIST); + taskAttributesMap = new HashMap(FIELD_LIST.length); + } + + /* Safely add a value from a date field (in case of null values) to the + taskAttributesMap. + */ + private void safePutDate(String field, Date value) { + if (value != null) { + taskAttributesMap.put(field, DateUtilities.getIso8601String(value)); + } + } + + // --- getters and setters + + @Override + public Date getCreationDate() { + return super.getCreationDate(); + } + + /* Build a HashMap of task fields and associated values. + */ + public HashMap getTaskAttributes() { + taskAttributesMap.put(AbstractController.KEY_ROWID, getTaskIdentifier().idAsString()); + taskAttributesMap.put(NAME, getName()); + taskAttributesMap.put(IMPORTANCE, getImportance().toString()); + taskAttributesMap.put(ELAPSED_SECONDS, getElapsedSeconds().toString()); + taskAttributesMap.put(ESTIMATED_SECONDS, getEstimatedSeconds().toString()); + safePutDate(TIMER_START, getTimerStart()); + safePutDate(DEFINITE_DUE_DATE, getDefiniteDueDate()); + safePutDate(PREFERRED_DUE_DATE, getPreferredDueDate()); + taskAttributesMap.put(NOTIFICATIONS, getNotificationIntervalSeconds().toString()); + taskAttributesMap.put(PROGRESS_PERCENTAGE, Integer.toString(getProgressPercentage())); + safePutDate(COMPLETION_DATE, getCompletionDate()); + safePutDate(CREATION_DATE, getCreationDate()); + safePutDate(HIDDEN_UNTIL, getHiddenUntil()); + taskAttributesMap.put(NOTES, getNotes()); + RepeatInfo repeat = getRepeat(); + if (repeat != null) { + taskAttributesMap.put(REPEAT_VALUE, Integer.toString(repeat.getValue())); + taskAttributesMap.put(REPEAT_INTERVAL, repeat.getInterval().toString()); + } + taskAttributesMap.put(FLAGS, Integer.toString(getFlags())); + taskAttributesMap.put(POSTPONE_COUNT, getPostponeCount().toString()); + taskAttributesMap.put(BLOCKING_ON, Long.toString(getBlockingOn().getId())); + safePutDate(LAST_NOTIFIED, getLastNotificationDate()); + taskAttributesMap.put(NOTIFICATION_FLAGS, Integer.toString(getNotificationFlags())); + String calendarUri = getCalendarUri(); + if (calendarUri != null) { + taskAttributesMap.put(CALENDAR_URI, calendarUri); + } + return taskAttributesMap; + } + + // --- setters + + public boolean setField(String field, String value) { + boolean success = true; + if(field.equals(NAME)) { + setName(value); + } + else if(field.equals(NOTES)) { + setNotes(value); + } + else if(field.equals(PROGRESS_PERCENTAGE)) { + setProgressPercentage(Integer.parseInt(value)); + } + else if(field.equals(IMPORTANCE)) { + setImportance(Importance.valueOf(value)); + } + else if(field.equals(ESTIMATED_SECONDS)) { + setEstimatedSeconds(Integer.parseInt(value)); + } + else if(field.equals(ELAPSED_SECONDS)) { + setElapsedSeconds(Integer.parseInt(value)); + } + else if(field.equals(TIMER_START)) { + setTimerStart(DateUtilities.getDateFromIso8601String(value)); + } + else if(field.equals(DEFINITE_DUE_DATE)) { + setDefiniteDueDate(DateUtilities.getDateFromIso8601String(value)); + } + else if(field.equals(PREFERRED_DUE_DATE)) { + setPreferredDueDate(DateUtilities.getDateFromIso8601String(value)); + } + else if(field.equals(HIDDEN_UNTIL)) { + setHiddenUntil(DateUtilities.getDateFromIso8601String(value)); + } + else if(field.equals(BLOCKING_ON)) { + setBlockingOn(new TaskIdentifier(Long.parseLong(value))); + } + else if(field.equals(POSTPONE_COUNT)) { + setPostponeCount(Integer.parseInt(value)); + } + else if(field.equals(NOTIFICATIONS)) { + setNotificationIntervalSeconds(Integer.parseInt(value)); + } + else if(field.equals(CREATION_DATE)) { + setCreationDate(DateUtilities.getDateFromIso8601String(value)); + } + else if(field.equals(COMPLETION_DATE)) { + setCompletionDate(DateUtilities.getDateFromIso8601String(value)); + } + else if(field.equals(NOTIFICATION_FLAGS)) { + setNotificationFlags(Integer.parseInt(value)); + } + else if(field.equals(LAST_NOTIFIED)) { + setLastNotificationTime(DateUtilities.getDateFromIso8601String(value)); + } + else if(field.equals(REPEAT_INTERVAL)) { + try { + setRepeatInterval(RepeatInterval.valueOf(value)); + } catch (Exception e) { + RepeatInterval repeatInterval; + switch (Integer.parseInt(value)) { + case R.string.repeat_days: + repeatInterval = RepeatInterval.DAYS; + break; + case R.string.repeat_weeks: + repeatInterval = RepeatInterval.WEEKS; + break; + case R.string.repeat_months: + repeatInterval = RepeatInterval.MONTHS; + break; + case R.string.repeat_hours: + repeatInterval = RepeatInterval.HOURS; + break; + default: + Log.e("XmlImport", "Unable to set repeat interval"); + repeatInterval = RepeatInterval.DAYS; + break; + } + setRepeatInterval(repeatInterval); + } + } + else if(field.equals(REPEAT_VALUE)) { + setRepeatValue(Integer.parseInt(value)); + } + else if(field.equals(FLAGS)) { + setFlags(Integer.parseInt(value)); + } + else { + success = false; + } + return success; + } + + public void setRepeatInterval(RepeatInterval repeatInterval) { + this.repeatInterval = repeatInterval; + if (repeatValue != null) { + setRepeat(new RepeatInfo(repeatInterval, repeatValue)); + } + } + + public void setRepeatValue(Integer repeatValue) { + this.repeatValue = repeatValue; + if (repeatInterval != null) { + setRepeat(new RepeatInfo(repeatInterval, repeatValue)); + } + } +} diff --git a/tests/src/com/todoroo/astrid/model/TaskTests.java b/tests/src/com/todoroo/astrid/model/TaskTests.java new file mode 100644 index 000000000..b053b01ce --- /dev/null +++ b/tests/src/com/todoroo/astrid/model/TaskTests.java @@ -0,0 +1,117 @@ +package com.todoroo.astrid.model; + +import java.util.ArrayList; +import java.util.HashSet; +import java.util.TreeSet; + +import android.content.ContentValues; + +import com.todoroo.andlib.service.Autowired; +import com.todoroo.andlib.test.utility.DateUtilities; +import com.todoroo.astrid.service.TaskService; +import com.todoroo.astrid.test.DatabaseTestCase; +import com.todoroo.astrid.utility.Preferences; + +public class TaskTests extends DatabaseTestCase { + + @Autowired + TaskService taskService; + + /** Sanity-check the constants */ + public void testSanity() { + assertTrue(Task.IMPORTANCE_DO_OR_DIE < Task.IMPORTANCE_MUST_DO); + assertTrue(Task.IMPORTANCE_MUST_DO < Task.IMPORTANCE_SHOULD_DO); + assertTrue(Task.IMPORTANCE_SHOULD_DO < Task.IMPORTANCE_NONE); + + ArrayList urgencies = new ArrayList(); + urgencies.add(Task.URGENCY_NONE); + urgencies.add(Task.URGENCY_SPECIFIC_DAY); + urgencies.add(Task.URGENCY_SPECIFIC_DAY_TIME); + urgencies.add(Task.URGENCY_THIS_MONTH); + urgencies.add(Task.URGENCY_THIS_WEEK); + urgencies.add(Task.URGENCY_TODAY); + urgencies.add(Task.URGENCY_WITHIN_A_YEAR); + urgencies.add(Task.URGENCY_WITHIN_SIX_MONTHS); + urgencies.add(Task.URGENCY_WITHIN_THREE_MONTHS); + + // assert no duplicates + assertEquals(new TreeSet(urgencies).size(), + urgencies.size()); + } + + /** Check defaults */ + public void checkDefaults() { + Preferences.setPreferenceDefaults(); + ContentValues defaults = new Task().getDefaultValues(); + assertTrue(defaults.containsKey(Task.TITLE.name)); + assertTrue(defaults.containsKey(Task.DUE_DATE.name)); + assertTrue(defaults.containsKey(Task.HIDDEN_UNTIL.name)); + assertTrue(defaults.containsKey(Task.COMPLETION_DATE.name)); + assertTrue(defaults.containsKey(Task.URGENCY.name)); + assertTrue(defaults.containsKey(Task.IMPORTANCE.name)); + } + + /** Check task gets a creation date at some point */ + public void checkCreationDate() { + Task task = new Task(); + taskService.save(task, false); + assertTrue(task.getValue(Task.CREATION_DATE) > 0); + } + + /** + * Check various getters + */ + public void checkGetters() { + Task task = new Task(); + assertFalse(task.isCompleted()); + task.setValue(Task.COMPLETION_DATE, DateUtilities.now()); + assertTrue(task.isCompleted()); + + task = new Task(); + assertFalse(task.isHidden()); + task.setValue(Task.HIDDEN_UNTIL, DateUtilities.now() + 1000); + assertTrue(task.isHidden()); + + task = new Task(); + assertFalse(task.hasDueDate()); + task.setValue(Task.DUE_DATE, DateUtilities.now() + 1000); + assertTrue(task.hasDueDate()); + + int[] colors = Task.getImportanceColors(getContext()); + assertEquals(Math.abs(Task.IMPORTANCE_NONE - Task.IMPORTANCE_DO_OR_DIE + 1), + colors.length); + HashSet set = new HashSet(); + for(int i = 0; i < colors.length; i++) { + assertFalse(set.contains(colors[i])); + set.add(colors[i]); + } + } + + public void checkDueDateInitialization() { + assertEquals(0, Task.initializeDueDate(Task.URGENCY_NONE)); + + int date = Task.initializeDueDate(Task.URGENCY_THIS_MONTH); + assertTrue(date > DateUtilities.now() + 27 * 24 * 3600); + assertTrue(date < DateUtilities.now() + 32 * 24 * 3600); + + date = Task.initializeDueDate(Task.URGENCY_THIS_WEEK); + assertTrue(date > DateUtilities.now() + 6 * 24 * 3600); + assertTrue(date < DateUtilities.now() + 8 * 24 * 3600); + + date = Task.initializeDueDate(Task.URGENCY_TODAY); + assertTrue(date > DateUtilities.now() - 60); + assertTrue(date < DateUtilities.now() + 24 * 3600); + + date = Task.initializeDueDate(Task.URGENCY_WITHIN_THREE_MONTHS); + assertTrue(date > DateUtilities.now() + 85 * 24 * 3600); + assertTrue(date < DateUtilities.now() + 95 * 24 * 3600); + + date = Task.initializeDueDate(Task.URGENCY_WITHIN_SIX_MONTHS); + assertTrue(date > DateUtilities.now() + 180 * 24 * 3600); + assertTrue(date < DateUtilities.now() + 185 * 24 * 3600); + + date = Task.initializeDueDate(Task.URGENCY_WITHIN_A_YEAR); + assertTrue(date > DateUtilities.now() + 364 * 24 * 3600); + assertTrue(date < DateUtilities.now() + 367 * 24 * 3600); + } +} diff --git a/tests/src/com/todoroo/astrid/provider/ProviderTests.java b/tests/src/com/todoroo/astrid/provider/ProviderTests.java new file mode 100644 index 000000000..ba1e74046 --- /dev/null +++ b/tests/src/com/todoroo/astrid/provider/ProviderTests.java @@ -0,0 +1,425 @@ +package com.todoroo.astrid.provider; + +import android.content.ContentResolver; +import android.content.ContentValues; +import android.database.Cursor; +import android.net.Uri; + +import com.todoroo.astrid.api.AstridContentProvider; +import com.todoroo.astrid.api.AstridContentProvider.AstridTask; +import com.todoroo.astrid.test.DatabaseTestCase; + +public class ProviderTests extends DatabaseTestCase { + + String[] PROJECTION = new String[] { + AstridTask.ID, + AstridTask.TITLE, + }; + + /** Test CRUD over tasks with the ALL ITEMS cursor */ + public void testAllItemsCrud() { + ContentResolver resolver = getContext().getContentResolver(); + + // fetch all tasks, get nothing + Uri uri = AstridContentProvider.allItemsUri(); + Cursor cursor = resolver.query(uri, PROJECTION, "1", null, null); + assertEquals(0, cursor.getCount()); + cursor.close(); + + // insert a task + ContentValues values = new ContentValues(); + values.put(AstridTask.TITLE, "mf doom?"); + resolver.insert(uri, values); + + // fetch all tasks, get something + cursor = resolver.query(uri, PROJECTION, "1", null, null); + assertEquals(1, cursor.getCount()); + cursor.moveToFirst(); + assertEquals("mf doom?", cursor.getString(1)); + cursor.close(); + + // delete a task + assertEquals(1, resolver.delete(uri, "1", null)); + + // fetch all tasks, get nothing + cursor = resolver.query(uri, PROJECTION, null, null, null); + assertEquals(0, cursor.getCount()); + cursor.close(); + } + + /** Test selecting data */ + public void testSelection() { + ContentResolver resolver = getContext().getContentResolver(); + Uri uri = AstridContentProvider.allItemsUri(); + + // insert some tasks + ContentValues values = new ContentValues(); + values.put(AstridTask.TITLE, "tujiko noriko"); + values.put(AstridTask.IMPORTANCE, AstridTask.IMPORTANCE_MUST_DO); + resolver.insert(uri, values); + + values.clear(); + values.put(AstridTask.TITLE, "miho asahi"); + values.put(AstridTask.IMPORTANCE, AstridTask.IMPORTANCE_NONE); + resolver.insert(uri, values); + + // fetch all tasks with various selection parameters + Cursor cursor = resolver.query(uri, PROJECTION, "1", null, null); + assertEquals(2, cursor.getCount()); + cursor.close(); + + cursor = resolver.query(uri, PROJECTION, null, null, null); + assertEquals(2, cursor.getCount()); + cursor.close(); + + cursor = resolver.query(uri, PROJECTION, AstridTask.IMPORTANCE + "=" + + AstridTask.IMPORTANCE_MUST_DO, null, null); + assertEquals(1, cursor.getCount()); + cursor.moveToFirst(); + assertEquals("tujiko noriko", cursor.getString(1)); + cursor.close(); + + cursor = resolver.query(uri, PROJECTION, AstridTask.IMPORTANCE + "<" + + AstridTask.IMPORTANCE_MUST_DO, null, null); + assertEquals(1, cursor.getCount()); + cursor.moveToFirst(); + assertEquals("miho asahi", cursor.getString(1)); + cursor.close(); + + cursor = resolver.query(uri, PROJECTION, AstridTask.IMPORTANCE + "=" + + AstridTask.IMPORTANCE_DO_OR_DIE, null, null); + assertEquals(0, cursor.getCount()); + cursor.close(); + } + + /** Test selecting data with metadata */ + public void testSelectionWithMetadata() { + ContentResolver resolver = getContext().getContentResolver(); + Uri uri = AstridContentProvider.allItemsUri(); + + // insert some tasks + ContentValues values = new ContentValues(); + values.put(AstridTask.TITLE, "turkoglu"); + values.put("suave-factor", "10"); + resolver.insert(uri, values); + + values.clear(); + values.put(AstridTask.TITLE, "ichiro"); + values.put("suave-factor", "30"); + resolver.insert(uri, values); + + values.clear(); + values.put(AstridTask.TITLE, "oprah"); + values.put("suave-factor", "-10"); + resolver.insert(uri, values); + + values.clear(); + values.put(AstridTask.TITLE, "cruise"); + values.put("suave-factor", "-10"); + resolver.insert(uri, values); + + values.clear(); + values.put(AstridTask.TITLE, "oprah"); + resolver.insert(uri, values); + + String[] projection = new String[] { + AstridTask.ID, + AstridTask.TITLE, + "suave-factor" + }; + + // fetch all tasks with various selection parameters + Cursor cursor = resolver.query(uri, projection, "1", null, null); + assertEquals(3, cursor.getCount()); + cursor.close(); + + cursor = resolver.query(uri, projection, "'suave-factor' = 30", null, null); + assertEquals(1, cursor.getCount()); + cursor.moveToFirst(); + assertEquals("ichiro", cursor.getString(1)); + cursor.close(); + + cursor = resolver.query(uri, projection, "'suave-factor' < 5", null, null); + assertEquals(1, cursor.getCount()); + cursor.moveToFirst(); + assertEquals("cruise", cursor.getString(1)); + cursor.close(); + + cursor = resolver.query(uri, projection, "'suave-factor' ISNULL", null, null); + assertEquals(1, cursor.getCount()); + cursor.moveToFirst(); + assertEquals("oprah", cursor.getString(1)); + cursor.close(); + } + + /** Test updating */ + public void testUpdating() { + ContentResolver resolver = getContext().getContentResolver(); + Uri allItemsUri = AstridContentProvider.allItemsUri(); + + // insert some tasks + ContentValues values = new ContentValues(); + values.put(AstridTask.TITLE, "carlos silva"); + values.put("suckitude", "acute"); + Uri carlosUri = resolver.insert(allItemsUri, values); + + values.clear(); + values.put(AstridTask.TITLE, "felix hernandez"); + values.put(AstridTask.URGENCY, AstridTask.URGENCY_WITHIN_A_YEAR); + resolver.insert(allItemsUri, values); + + String[] projection = new String[] { + AstridTask.ID, + AstridTask.TITLE, + "suckitude" + }; + + // test updating with single item URI + Cursor cursor = resolver.query(allItemsUri, projection, "'suckitude' = 'carlos who?'", null, null); + assertEquals(0, cursor.getCount()); + cursor.close(); + + values.clear(); + values.put("suckitude", "carlos who?"); + assertEquals(1, resolver.update(carlosUri, values, null, null)); + + cursor = resolver.query(allItemsUri, projection, "'suckitude' = 'carlos who?'", null, null); + assertEquals(1, cursor.getCount()); + cursor.close(); + + // test updating with all items uri + cursor = resolver.query(allItemsUri, PROJECTION, AstridTask.URGENCY + " = " + + AstridTask.URGENCY_NONE, null, null); + assertEquals(0, cursor.getCount()); + cursor.close(); + + values.clear(); + values.put(AstridTask.URGENCY, AstridTask.URGENCY_NONE); + assertEquals(1, resolver.update(allItemsUri, values, AstridTask.URGENCY + + "=" + AstridTask.URGENCY_WITHIN_A_YEAR, null)); + + cursor = resolver.query(allItemsUri, PROJECTION, AstridTask.URGENCY + " = " + + AstridTask.URGENCY_NONE, null, null); + assertEquals(1, cursor.getCount()); + cursor.close(); + + // test updating with group by uri + try { + Uri groupByUri = AstridContentProvider.groupByUri(AstridTask.TITLE); + resolver.update(groupByUri, values, null, null); + fail("Able to update using groupby uri"); + } catch (Exception e) { + // expected + } + } + + /** Test deleting */ + public void testDeleting() { + ContentResolver resolver = getContext().getContentResolver(); + Uri allItemsUri = AstridContentProvider.allItemsUri(); + + // insert some tasks + ContentValues values = new ContentValues(); + values.put(AstridTask.TITLE, "modest mouse"); + values.put(AstridTask.IMPORTANCE, AstridTask.IMPORTANCE_DO_OR_DIE); + Uri modestMouse = resolver.insert(allItemsUri, values); + + values.clear(); + values.put(AstridTask.TITLE, "death cab"); + values.put(AstridTask.IMPORTANCE, AstridTask.IMPORTANCE_MUST_DO); + resolver.insert(allItemsUri, values); + + values.clear(); + values.put(AstridTask.TITLE, "murder city devils"); + values.put(AstridTask.IMPORTANCE, AstridTask.IMPORTANCE_SHOULD_DO); + resolver.insert(allItemsUri, values); + + // test deleting with single URI + Cursor cursor = resolver.query(allItemsUri, PROJECTION, AstridTask.TITLE + + " = 'modest mouse'", null, null); + assertEquals(1, cursor.getCount()); + cursor.close(); + + assertEquals(1, resolver.delete(modestMouse, null, null)); + + cursor = resolver.query(allItemsUri, PROJECTION, AstridTask.TITLE + + " = 'modest mouse'", null, null); + assertEquals(0, cursor.getCount()); + cursor.close(); + + // test updating with all items uri + cursor = resolver.query(allItemsUri, PROJECTION, AstridTask.TITLE + + " = 'murder city devils'", null, null); + assertEquals(1, cursor.getCount()); + cursor.close(); + + assertEquals(1, resolver.delete(allItemsUri, AstridTask.IMPORTANCE + + "<" + AstridTask.IMPORTANCE_MUST_DO, null)); + + cursor = resolver.query(allItemsUri, PROJECTION, AstridTask.TITLE + + " = 'murder city devils'", null, null); + assertEquals(0, cursor.getCount()); + cursor.close(); + + // test with group by uri + try { + Uri groupByUri = AstridContentProvider.groupByUri(AstridTask.TITLE); + resolver.delete(groupByUri, null, null); + fail("Able to delete using groupby uri"); + } catch (Exception e) { + // expected + } + } + + /** Test CRUD over SINGLE ITEM uri */ + public void testSingleItemCrud() { + ContentResolver resolver = getContext().getContentResolver(); + + Uri uri = AstridContentProvider.allItemsUri(); + + ContentValues values = new ContentValues(); + values.put(AstridTask.TITLE, "mf doom?"); + Uri firstUri = resolver.insert(uri, values); + + values.put(AstridTask.TITLE, "gm grimm!"); + Uri secondUri = resolver.insert(uri, values); + assertNotSame(firstUri, secondUri); + + Cursor cursor = resolver.query(uri, PROJECTION, "1", null, null); + assertEquals(2, cursor.getCount()); + cursor.close(); + + cursor = resolver.query(firstUri, PROJECTION, null, null, null); + assertEquals(1, cursor.getCount()); + cursor.moveToFirst(); + assertEquals("mf doom?", cursor.getString(1)); + cursor.close(); + + values.put(AstridTask.TITLE, "danger mouse."); + resolver.update(firstUri, values, null, null); + cursor = resolver.query(firstUri, PROJECTION, null, null, null); + assertEquals(1, cursor.getCount()); + cursor.moveToFirst(); + assertEquals("danger mouse.", cursor.getString(1)); + cursor.close(); + + assertEquals(1, resolver.delete(firstUri, null, null)); + + cursor = resolver.query(uri, PROJECTION, null, null, null); + assertEquals(1, cursor.getCount()); + cursor.close(); + } + + /** Test GROUP BY uri */ + public void testGroupByCrud() { + ContentResolver resolver = getContext().getContentResolver(); + Uri uri = AstridContentProvider.allItemsUri(); + + ContentValues values = new ContentValues(); + values.put(AstridTask.TITLE, "catwoman"); + resolver.insert(uri, values); + + values.put(AstridTask.TITLE, "the joker"); + resolver.insert(uri, values); + resolver.insert(uri, values); + resolver.insert(uri, values); + + values.put(AstridTask.TITLE, "deep freeze"); + resolver.insert(uri, values); + resolver.insert(uri, values); + + Uri groupByUri = AstridContentProvider.groupByUri(AstridTask.TITLE); + Cursor cursor = resolver.query(groupByUri, PROJECTION, null, null, AstridTask.TITLE); + assertEquals(3, cursor.getCount()); + cursor.moveToFirst(); + assertEquals("catwoman", cursor.getString(1)); + cursor.moveToNext(); + assertEquals("deep freeze", cursor.getString(1)); + cursor.moveToNext(); + assertEquals("the joker", cursor.getString(1)); + cursor.close(); + + // test "group-by" with metadata + values.put("age", 50); + values.put("size", "large"); + resolver.insert(uri, values); + values.put("age", 40); + values.put("size", "large"); + resolver.insert(uri, values); + + values.put("age", 20); + values.put("size", "small"); + resolver.insert(uri, values); + + Uri groupByAgeUri = AstridContentProvider.groupByUri("size"); + cursor = resolver.query(groupByUri, PROJECTION, null, null, AstridTask.TITLE); + assertEquals(3, cursor.getCount()); + } + + /** Test updating and deleting with metadata */ + public void testMetadataUpdateDelete() { + ContentResolver resolver = getContext().getContentResolver(); + Uri allItemsUri = AstridContentProvider.allItemsUri(); + + // insert some tasks + ContentValues values = new ContentValues(); + values.put(AstridTask.TITLE, "chicago"); + values.put("pizza", "delicious"); + values.put("temperature", 20); + resolver.insert(allItemsUri, values); + + values.clear(); + values.put(AstridTask.TITLE, "san francisco"); + values.put("pizza", "meh"); + values.put("temperature", 60); + resolver.insert(allItemsUri, values); + + values.clear(); + values.put(AstridTask.TITLE, "new york"); + values.put("pizza", "yum"); + values.put("temperature", 30); + resolver.insert(allItemsUri, values); + + // test updating with standard URI (shouldn't work) + values.clear(); + values.put("pizza", "nonexistent, the city is underwater"); + assertEquals(0, resolver.update(allItemsUri, values, + "'pizza'='yum'", null)); + + String[] projection = new String[] { + AstridTask.ID, + AstridTask.TITLE, + "pizza", + "temperature" + }; + + Cursor cursor = resolver.query(allItemsUri, projection, "'pizza' = 'yum'", null, null); + assertEquals(1, cursor.getCount()); + cursor.close(); + + // test updating with metadata uri + Uri pizzaUri = AstridContentProvider.allItemsWithMetadataUri(new String[] {"pizza"}); + assertEquals(1, resolver.update(pizzaUri, values, + "'pizza'='yum'", null)); + + cursor = resolver.query(allItemsUri, projection, "'pizza' = 'yum'", null, null); + assertEquals(0, cursor.getCount()); + cursor.close(); + + // test deleting with metadata uri + Uri pizzaTempUri = AstridContentProvider.allItemsWithMetadataUri(new String[] {"pizza", + "temperature"}); + + cursor = resolver.query(allItemsUri, projection, AstridTask.TITLE + " = 'chicago'", null, null); + assertEquals(0, cursor.getCount()); + cursor.close(); + + // SQLITE: the deliverer of BAD NEWS + assertEquals(0, resolver.delete(pizzaTempUri, "pizza='delicious' AND temperature > 50", null)); + assertEquals(1, resolver.delete(pizzaTempUri, "pizza='delicious' AND temperature < 50", null)); + + cursor = resolver.query(allItemsUri, projection, AstridTask.TITLE + " = 'chicago'", null, null); + assertEquals(0, cursor.getCount()); + cursor.close(); + } +} diff --git a/tests/src/com/todoroo/astrid/service/AstridDependencyInjectorTests.java b/tests/src/com/todoroo/astrid/service/AstridDependencyInjectorTests.java new file mode 100644 index 000000000..537a347c9 --- /dev/null +++ b/tests/src/com/todoroo/astrid/service/AstridDependencyInjectorTests.java @@ -0,0 +1,90 @@ +package com.todoroo.astrid.service; + +import android.test.AndroidTestCase; + +import com.timsu.astrid.R; +import com.todoroo.andlib.service.AbstractDependencyInjector; +import com.todoroo.andlib.service.Autowired; +import com.todoroo.andlib.service.DependencyInjectionService; +import com.todoroo.andlib.utility.DialogUtilities; + +public class AstridDependencyInjectorTests extends AndroidTestCase { + + protected static class Helper { + public Object getObject() { + return null; + } + } + + @Override + protected void setUp() throws Exception { + super.setUp(); + + // in case some state from other unit tests overwrote injector + DependencyInjectionService.getInstance().setInjectors(new AbstractDependencyInjector[] { + new AstridDependencyInjector() + }); + } + + public void testWithString() { + Helper helper = new Helper() { + @Autowired + public String applicationName; + + @Override + public Object getObject() { + return applicationName; + }; + }; + + DependencyInjectionService.getInstance().inject(helper); + assertTrue(((String)helper.getObject()).length() > 0); + } + + public void testWithInteger() { + + Helper helper = new Helper() { + @Autowired + public Integer informationDialogTitleResource; + + @Override + public Object getObject() { + return informationDialogTitleResource; + }; + }; + + DependencyInjectionService.getInstance().inject(helper); + assertEquals(R.string.DLG_information_title, helper.getObject()); + } + + public void testWithClass() { + + Helper helper = new Helper() { + @Autowired + public DialogUtilities dialogUtilities; + + @Override + public Object getObject() { + return dialogUtilities; + }; + }; + + DependencyInjectionService.getInstance().inject(helper); + assertTrue(helper.getObject() instanceof DialogUtilities); + + Helper helper2 = new Helper() { + @Autowired + public DialogUtilities dialogUtilities; + + @Override + public Object getObject() { + return dialogUtilities; + }; + }; + + DependencyInjectionService.getInstance().inject(helper2); + + assertEquals(helper.getObject(), helper2.getObject()); + + } +} diff --git a/tests/src/com/todoroo/astrid/service/TaskServiceTests.java b/tests/src/com/todoroo/astrid/service/TaskServiceTests.java new file mode 100644 index 000000000..31f587571 --- /dev/null +++ b/tests/src/com/todoroo/astrid/service/TaskServiceTests.java @@ -0,0 +1,139 @@ +package com.todoroo.astrid.service; +import com.todoroo.andlib.service.Autowired; +import com.todoroo.andlib.test.data.TodorooCursor; +import com.todoroo.andlib.test.utility.DateUtilities; +import com.todoroo.astrid.api.Filter; +import com.todoroo.astrid.dao.Database; +import com.todoroo.astrid.dao.TaskDao; +import com.todoroo.astrid.model.Task; +import com.todoroo.astrid.test.DatabaseTestCase; + + +public class TaskServiceTests extends DatabaseTestCase { + + @Autowired + TaskService taskService; + + @Autowired + TaskDao taskDao; + + /** + * Since a lot of the service-layer methods call through to the dao, + * we don't need to do a lot of extensive testing here, just various + * boundary conditions. + */ + public void testTaskDaoExposedMethods() { + Task task = new Task(); + task.setValue(Task.TITLE, "normal"); + assertTrue(taskService.save(task, false)); + + // empty fetch w/ filter + Filter filter = new Filter("bla", "bla", "WHERE 1", null); + TodorooCursor cursor = + taskService.fetchFiltered(TaskService.idProperties(), filter); + assertEquals(1, cursor.getCount()); + cursor.close(); + + filter.sqlQuery = "WHERE " + Task.TITLE + " = \"bob\""; + cursor = taskService.fetchFiltered(TaskService.idProperties(), filter); + assertEquals(0, cursor.getCount()); + cursor.close(); + + Task fetched = taskService.fetchById(TaskService.idProperties(), task.getId()); + assertNotNull(fetched); + fetched = taskService.fetchById(TaskService.idProperties(), task.getId() + 1); + assertNull(fetched); + + taskService.setComplete(task, true); + assertTrue(task.isCompleted()); + + fetched = taskService.fetchById(Task.PROPERTIES, task.getId()); + assertTrue(fetched.isCompleted()); + taskService.setComplete(task, false); + fetched = taskService.fetchById(Task.PROPERTIES, task.getId()); + assertFalse(fetched.isCompleted()); + assertFalse(task.isCompleted()); + + long id = task.getId(); + taskService.delete(id); + fetched = taskService.fetchById(Task.PROPERTIES, id); + assertNull(fetched); + } + + /** + * Test cleanup + */ + public void testCleanup() throws Exception { + TodorooCursor cursor; + + // save task with a name + Task task1 = new Task(); + task1.setValue(Task.TITLE, "normal"); + assertTrue(taskService.save(task1, false)); + + // save task without a name + Task task2 = new Task(); + task2.setValue(Task.TITLE, ""); + task2.setValue(Task.HIDDEN_UNTIL, DateUtilities.now() + 10000); + assertTrue(taskService.save(task2, false)); + + // save task #2 without a name + Task task3 = new Task(); + task3.setValue(Task.TITLE, ""); + task3.setValue(Task.DUE_DATE, DateUtilities.now() + 10000); + assertTrue(taskService.save(task3, false)); + + cursor = taskDao.fetch(database, Task.PROPERTIES, null, null); + assertEquals(3, cursor.getCount()); + cursor.close(); + + taskService.cleanup(); + + cursor = taskDao.fetch(database, Task.PROPERTIES, null, null); + assertEquals(1, cursor.getCount()); + cursor.close(); + } + + /** + * Test the sql invoked from a filter on new task creation + */ + public void testInvokeNewTaskSql() { + TodorooCursor cursor; + Filter filter = new Filter("bla", "bla", "WHERE 1", null); + + Task task = new Task(); + task.setValue(Task.TITLE, "evils"); + assertTrue(taskService.save(task, false)); + + cursor = taskDao.fetch(database, Task.PROPERTIES, null, null); + assertEquals(1, cursor.getCount()); + cursor.moveToNext(); + task = new Task(cursor, Task.PROPERTIES); + assertEquals("evils", task.getValue(Task.TITLE)); + cursor.close(); + + filter.sqlForNewTasks = String.format("UPDATE %s set %s = \"good\"", + Database.TASK_TABLE, Task.TITLE); + taskService.invokeSqlForNewTask(filter, task); + + cursor = taskDao.fetch(database, Task.PROPERTIES, null, null); + assertEquals(1, cursor.getCount()); + cursor.moveToNext(); + task = new Task(cursor, Task.PROPERTIES); + assertEquals("good", task.getValue(Task.TITLE)); + cursor.close(); + + filter.sqlForNewTasks = String.format("UPDATE %s set %s = \"yum\" WHERE %s = $ID", + Database.TASK_TABLE, Task.TITLE, Task.ID); + taskService.invokeSqlForNewTask(filter, task); + + cursor = taskDao.fetch(database, Task.PROPERTIES, null, null); + assertEquals(1, cursor.getCount()); + cursor.moveToNext(); + task = new Task(cursor, Task.PROPERTIES); + assertEquals("yum", task.getValue(Task.TITLE)); + cursor.close(); + } + + +} diff --git a/tests/src/com/todoroo/astrid/test/AstridActivityTestCase.java b/tests/src/com/todoroo/astrid/test/AstridActivityTestCase.java new file mode 100644 index 000000000..b44a96a34 --- /dev/null +++ b/tests/src/com/todoroo/astrid/test/AstridActivityTestCase.java @@ -0,0 +1,96 @@ +package com.todoroo.astrid.test; + +import android.app.Activity; +import android.content.pm.ActivityInfo; +import android.test.ActivityInstrumentationTestCase2; +import android.view.MotionEvent; + +import com.todoroo.andlib.service.Autowired; +import com.todoroo.andlib.service.DependencyInjectionService; +import com.todoroo.andlib.service.TestDependencyInjector; +import com.todoroo.astrid.dao.Database; +import com.todoroo.astrid.service.AstridDependencyInjector; +import com.todoroo.astrid.test.DatabaseTestCase.AstridTestDatabase; + +/** + * ActivityTestCase is a helper for testing Todoroo Activities. + *

+ * It initializes a test database before the activity is created. + * + * @author Tim Su + * + * @param + */ +public class AstridActivityTestCase extends ActivityInstrumentationTestCase2 { + + @Autowired + public Database database; + + static { + AstridDependencyInjector.initialize(); + TestDependencyInjector.initialize("db").addInjectable("database", + new AstridTestDatabase()); + } + + public AstridActivityTestCase(String packageName, Class activityClass) { + super(packageName, activityClass); + DependencyInjectionService.getInstance().inject(this); + } + + @Override + protected void setUp() throws Exception { + super.setUp(); + + // create new test database + AstridTestDatabase.dropTables(getInstrumentation().getTargetContext()); + database.openForWriting(); + } + + @Override + protected void tearDown() throws Exception { + super.tearDown(); + + if(database != null) + database.close(); + } + + /** + * Call to just tear this activity down + */ + protected void tearActivityDown() throws Exception { + super.tearDown(); + } + + /** + * Call to just set this activity up + */ + protected void setActivityUp() throws Exception { + super.setUp(); + } + + /** Calls various lifecycle methods, makes sure all of them work */ + public void testLifecycle() throws Exception { + T activity = getActivity(); + getInstrumentation().callActivityOnPause(activity); + Thread.sleep(500); + getInstrumentation().callActivityOnResume(activity); + + getInstrumentation().sendPointerSync(MotionEvent.obtain(500, 500, MotionEvent.ACTION_DOWN, 10, 30, 0)); + + getInstrumentation().callActivityOnPause(activity); + getInstrumentation().callActivityOnStop(activity); + Thread.sleep(500); + getInstrumentation().callActivityOnRestart(activity); + getInstrumentation().callActivityOnStart(activity); + getInstrumentation().callActivityOnResume(activity); + + getInstrumentation().sendPointerSync(MotionEvent.obtain(500, 500, MotionEvent.ACTION_DOWN, 10, 30, 0)); + + activity.setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE); + Thread.sleep(500); + activity.setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT); + Thread.sleep(1000); + } + + +} diff --git a/tests/src/com/todoroo/astrid/test/DatabaseTestCase.java b/tests/src/com/todoroo/astrid/test/DatabaseTestCase.java new file mode 100644 index 000000000..6423d53ae --- /dev/null +++ b/tests/src/com/todoroo/astrid/test/DatabaseTestCase.java @@ -0,0 +1,70 @@ +package com.todoroo.astrid.test; + +import android.content.Context; +import android.database.sqlite.SQLiteOpenHelper; + +import com.todoroo.andlib.service.Autowired; +import com.todoroo.andlib.service.TestDependencyInjector; +import com.todoroo.andlib.test.TodorooTestCase; +import com.todoroo.astrid.dao.Database; +import com.todoroo.astrid.dao.Database.AstridSQLiteOpenHelper; +import com.todoroo.astrid.service.AstridDependencyInjector; + +/** + * Test case that automatically sets up and tears down a test database + * + * @author Tim Su + * + */ +public class DatabaseTestCase extends TodorooTestCase { + + @Autowired + public Database database; + + static { + AstridDependencyInjector.initialize(); + TestDependencyInjector.initialize("db").addInjectable("database", + new AstridTestDatabase()); + } + + @Override + protected void setUp() throws Exception { + // create new test database + AstridTestDatabase.dropTables(getContext()); + database.openForWriting(); + + super.setUp(); + } + + @Override + protected void tearDown() throws Exception { + database.close(); + } + + public static class AstridTestDatabase extends Database { + + private static final String NAME = "todoroo-test"; + + @Override + public synchronized void openForWriting() { + if(helper == null) + helper = new AstridSQLiteOpenHelper(context, NAME, null, VERSION); + super.openForWriting(); + } + + @Override + public synchronized void openForReading() { + if(helper == null) + helper = new AstridSQLiteOpenHelper(context, NAME, null, VERSION); + super.openForWriting(); + } + + public static void dropTables(Context context) { + // force drop database + SQLiteOpenHelper helper = new AstridSQLiteOpenHelper(context, NAME, null, VERSION); + helper.onUpgrade(helper.getWritableDatabase(), + 0, VERSION); + helper.close(); + } + } +} diff --git a/tests/src/com/todoroo/astrid/upgrade/Astrid2To3UpgradeTests.java b/tests/src/com/todoroo/astrid/upgrade/Astrid2To3UpgradeTests.java new file mode 100644 index 000000000..96b186486 --- /dev/null +++ b/tests/src/com/todoroo/astrid/upgrade/Astrid2To3UpgradeTests.java @@ -0,0 +1,7 @@ +package com.todoroo.astrid.upgrade; + +import com.todoroo.astrid.test.DatabaseTestCase; + +public class Astrid2To3UpgradeTests extends DatabaseTestCase { + +}