--- /dev/null
+*.iml
+.gradle
+/local.properties
+/.idea/caches
+/.idea/libraries
+/.idea/modules.xml
+/.idea/workspace.xml
+/.idea/navEditor.xml
+/.idea/assetWizardSettings.xml
+.DS_Store
+/build
+/captures
+.externalNativeBuild
+.cxx
+local.properties
--- /dev/null
+<?xml version="1.0" encoding="UTF-8"?>
+<project version="4">
+ <component name="AnalysisProjectProfileManager">
+ <option name="PROJECT_PROFILE" />
+ <option name="USE_PROJECT_LEVEL_SETTINGS" value="false" />
+ <list size="0" />
+ </component>
+ <component name="SuppressionsComponent">
+ <option name="suppComments" value="[]" />
+ </component>
+</project>
\ No newline at end of file
--- /dev/null
+# Default ignored files
+/shelf/
+/workspace.xml
--- /dev/null
+<component name="ProjectCodeStyleConfiguration">
+ <code_scheme name="Project" version="173">
+ <JetCodeStyleSettings>
+ <option name="PACKAGES_TO_USE_STAR_IMPORTS">
+ <value>
+ <package name="java.util" withSubpackages="false" static="false" />
+ <package name="kotlinx.android.synthetic" withSubpackages="true" static="false" />
+ <package name="io.ktor" withSubpackages="true" static="false" />
+ </value>
+ </option>
+ <option name="CODE_STYLE_DEFAULTS" value="KOTLIN_OFFICIAL" />
+ </JetCodeStyleSettings>
+ <codeStyleSettings language="XML">
+ <option name="FORCE_REARRANGE_MODE" value="1" />
+ <indentOptions>
+ <option name="CONTINUATION_INDENT_SIZE" value="4" />
+ </indentOptions>
+ <arrangement>
+ <rules>
+ <section>
+ <rule>
+ <match>
+ <AND>
+ <NAME>xmlns:android</NAME>
+ <XML_ATTRIBUTE />
+ <XML_NAMESPACE>^$</XML_NAMESPACE>
+ </AND>
+ </match>
+ </rule>
+ </section>
+ <section>
+ <rule>
+ <match>
+ <AND>
+ <NAME>xmlns:.*</NAME>
+ <XML_ATTRIBUTE />
+ <XML_NAMESPACE>^$</XML_NAMESPACE>
+ </AND>
+ </match>
+ <order>BY_NAME</order>
+ </rule>
+ </section>
+ <section>
+ <rule>
+ <match>
+ <AND>
+ <NAME>.*:id</NAME>
+ <XML_ATTRIBUTE />
+ <XML_NAMESPACE>http://schemas.android.com/apk/res/android</XML_NAMESPACE>
+ </AND>
+ </match>
+ </rule>
+ </section>
+ <section>
+ <rule>
+ <match>
+ <AND>
+ <NAME>.*:name</NAME>
+ <XML_ATTRIBUTE />
+ <XML_NAMESPACE>http://schemas.android.com/apk/res/android</XML_NAMESPACE>
+ </AND>
+ </match>
+ </rule>
+ </section>
+ <section>
+ <rule>
+ <match>
+ <AND>
+ <NAME>name</NAME>
+ <XML_ATTRIBUTE />
+ <XML_NAMESPACE>^$</XML_NAMESPACE>
+ </AND>
+ </match>
+ </rule>
+ </section>
+ <section>
+ <rule>
+ <match>
+ <AND>
+ <NAME>style</NAME>
+ <XML_ATTRIBUTE />
+ <XML_NAMESPACE>^$</XML_NAMESPACE>
+ </AND>
+ </match>
+ </rule>
+ </section>
+ <section>
+ <rule>
+ <match>
+ <AND>
+ <NAME>.*</NAME>
+ <XML_ATTRIBUTE />
+ <XML_NAMESPACE>^$</XML_NAMESPACE>
+ </AND>
+ </match>
+ <order>BY_NAME</order>
+ </rule>
+ </section>
+ <section>
+ <rule>
+ <match>
+ <AND>
+ <NAME>.*</NAME>
+ <XML_ATTRIBUTE />
+ <XML_NAMESPACE>http://schemas.android.com/apk/res/android</XML_NAMESPACE>
+ </AND>
+ </match>
+ <order>ANDROID_ATTRIBUTE_ORDER</order>
+ </rule>
+ </section>
+ <section>
+ <rule>
+ <match>
+ <AND>
+ <NAME>.*</NAME>
+ <XML_ATTRIBUTE />
+ <XML_NAMESPACE>.*</XML_NAMESPACE>
+ </AND>
+ </match>
+ <order>BY_NAME</order>
+ </rule>
+ </section>
+ </rules>
+ </arrangement>
+ </codeStyleSettings>
+ <codeStyleSettings language="kotlin">
+ <option name="CODE_STYLE_DEFAULTS" value="KOTLIN_OFFICIAL" />
+ </codeStyleSettings>
+ </code_scheme>
+</component>
\ No newline at end of file
--- /dev/null
+<component name="ProjectCodeStyleConfiguration">
+ <state>
+ <option name="USE_PER_PROJECT_SETTINGS" value="true" />
+ </state>
+</component>
\ No newline at end of file
--- /dev/null
+<?xml version="1.0" encoding="UTF-8"?>
+<project version="4">
+ <component name="CompilerConfiguration">
+ <bytecodeTargetLevel target="1.8" />
+ </component>
+</project>
\ No newline at end of file
--- /dev/null
+<component name="ProjectDictionaryState">
+ <dictionary name="Administrator">
+ <words>
+ <w>imei</w>
+ </words>
+ </dictionary>
+</component>
\ No newline at end of file
--- /dev/null
+<component name="ProjectDictionaryState">
+ <dictionary name="HDC">
+ <words>
+ <w>gson</w>
+ </words>
+ </dictionary>
+</component>
\ No newline at end of file
--- /dev/null
+<?xml version="1.0" encoding="UTF-8"?>
+<project version="4">
+ <component name="GradleMigrationSettings" migrationVersion="1" />
+ <component name="GradleSettings">
+ <option name="linkedExternalProjectsSettings">
+ <GradleProjectSettings>
+ <option name="testRunner" value="PLATFORM" />
+ <option name="disableWrapperSourceDistributionNotification" value="true" />
+ <option name="distributionType" value="DEFAULT_WRAPPED" />
+ <option name="externalProjectPath" value="$PROJECT_DIR$" />
+ <option name="gradleJvm" value="1.8" />
+ <option name="modules">
+ <set>
+ <option value="$PROJECT_DIR$" />
+ <option value="$PROJECT_DIR$/app" />
+ </set>
+ </option>
+ <option name="resolveModulePerSourceSet" value="false" />
+ <option name="useQualifiedModuleNames" value="true" />
+ </GradleProjectSettings>
+ </option>
+ </component>
+</project>
\ No newline at end of file
--- /dev/null
+<?xml version="1.0" encoding="UTF-8"?>
+<project version="4">
+ <component name="RemoteRepositoriesConfiguration">
+ <remote-repository>
+ <option name="id" value="central" />
+ <option name="name" value="Maven Central repository" />
+ <option name="url" value="https://repo1.maven.org/maven2" />
+ </remote-repository>
+ <remote-repository>
+ <option name="id" value="jboss.community" />
+ <option name="name" value="JBoss Community repository" />
+ <option name="url" value="https://repository.jboss.org/nexus/content/repositories/public/" />
+ </remote-repository>
+ <remote-repository>
+ <option name="id" value="BintrayJCenter" />
+ <option name="name" value="BintrayJCenter" />
+ <option name="url" value="https://jcenter.bintray.com/" />
+ </remote-repository>
+ <remote-repository>
+ <option name="id" value="Google" />
+ <option name="name" value="Google" />
+ <option name="url" value="https://dl.google.com/dl/android/maven2/" />
+ </remote-repository>
+ <remote-repository>
+ <option name="id" value="maven" />
+ <option name="name" value="maven" />
+ <option name="url" value="http://maven.aliyun.com/nexus/content/groups/public/" />
+ </remote-repository>
+ <remote-repository>
+ <option name="id" value="maven2" />
+ <option name="name" value="maven2" />
+ <option name="url" value="http://maven.aliyun.com/nexus/content/repositories/jcenter" />
+ </remote-repository>
+ </component>
+</project>
\ No newline at end of file
--- /dev/null
+<?xml version="1.0" encoding="UTF-8"?>
+<project version="4">
+ <component name="FindBugsConfigurable">
+ <option name="make" value="true" />
+ <option name="effort" value="default" />
+ <option name="priority" value="Medium" />
+ <option name="excludeFilter" value="" />
+ </component>
+ <component name="ProjectRootManager" version="2" languageLevel="JDK_1_8" default="true" project-jdk-name="1.8" project-jdk-type="JavaSDK">
+ <output url="file://$PROJECT_DIR$/build/classes" />
+ </component>
+ <component name="ProjectType">
+ <option name="id" value="Android" />
+ </component>
+ <component name="SuppressionsComponent">
+ <option name="suppComments" value="[]" />
+ </component>
+</project>
\ No newline at end of file
--- /dev/null
+<?xml version="1.0" encoding="UTF-8"?>
+<project version="4">
+ <component name="AnalysisProjectProfileManager">
+ <option name="PROJECT_PROFILE" value="Project Default" />
+ <option name="USE_PROJECT_LEVEL_SETTINGS" value="false" />
+ <scopes />
+ <profiles>
+ <profile profile_name="Project Default" version="1.0" is_locked="false">
+ <coding_rule class="AM_CREATES_EMPTY_JAR_FILE_ENTRY" level="MAJOR" enabled="true" />
+ <coding_rule class="AM_CREATES_EMPTY_ZIP_FILE_ENTRY" level="MAJOR" enabled="true" />
+ <coding_rule class="AT_OPERATION_SEQUENCE_ON_CONCURRENT_ABSTRACTION" level="CRITICAL" enabled="false" />
+ <coding_rule class="BAC_BAD_APPLET_CONSTRUCTOR" level="MAJOR" enabled="false" />
+ <coding_rule class="BC_BAD_CAST_TO_ABSTRACT_COLLECTION" level="MAJOR" enabled="true" />
+ <coding_rule class="BC_BAD_CAST_TO_CONCRETE_COLLECTION" level="CRITICAL" enabled="true" />
+ <coding_rule class="BC_EQUALS_METHOD_SHOULD_WORK_FOR_ALL_OBJECTS" level="CRITICAL" enabled="true" />
+ <coding_rule class="BC_IMPOSSIBLE_CAST" level="BLOCKER" enabled="true" />
+ <coding_rule class="BC_IMPOSSIBLE_DOWNCAST" level="MAJOR" enabled="false" />
+ <coding_rule class="BC_IMPOSSIBLE_DOWNCAST_OF_TOARRAY" level="MAJOR" enabled="false" />
+ <coding_rule class="BC_IMPOSSIBLE_INSTANCEOF" level="CRITICAL" enabled="true" />
+ <coding_rule class="BC_UNCONFIRMED_CAST" level="CRITICAL" enabled="true" />
+ <coding_rule class="BC_UNCONFIRMED_CAST_OF_RETURN_VALUE" level="CRITICAL" enabled="false" />
+ <coding_rule class="BC_VACUOUS_INSTANCEOF" level="CRITICAL" enabled="true" />
+ <coding_rule class="BIT_ADD_OF_SIGNED_BYTE" level="CRITICAL" enabled="true" />
+ <coding_rule class="BIT_AND" level="CRITICAL" enabled="true" />
+ <coding_rule class="BIT_AND_ZZ" level="CRITICAL" enabled="true" />
+ <coding_rule class="BIT_IOR" level="CRITICAL" enabled="true" />
+ <coding_rule class="BIT_IOR_OF_SIGNED_BYTE" level="CRITICAL" enabled="true" />
+ <coding_rule class="BIT_SIGNED_CHECK" level="CRITICAL" enabled="true" />
+ <coding_rule class="BIT_SIGNED_CHECK_HIGH_BIT" level="CRITICAL" enabled="true" />
+ <coding_rule class="BOA_BADLY_OVERRIDDEN_ADAPTER" level="CRITICAL" enabled="true" />
+ <coding_rule class="BSHIFT_WRONG_ADD_PRIORITY" level="MAJOR" enabled="false" />
+ <coding_rule class="BX_BOXING_IMMEDIATELY_UNBOXED" level="MAJOR" enabled="true" />
+ <coding_rule class="BX_BOXING_IMMEDIATELY_UNBOXED_TO_PERFORM_COERCION" level="MAJOR" enabled="true" />
+ <coding_rule class="BX_UNBOXED_AND_COERCED_FOR_TERNARY_OPERATOR" level="MAJOR" enabled="true" />
+ <coding_rule class="BX_UNBOXING_IMMEDIATELY_REBOXED" level="CRITICAL" enabled="false" />
+ <coding_rule class="CAA_COVARIANT_ARRAY_ELEMENT_STORE" level="MAJOR" enabled="false" />
+ <coding_rule class="CAA_COVARIANT_ARRAY_FIELD" level="MAJOR" enabled="false" />
+ <coding_rule class="CAA_COVARIANT_ARRAY_LOCAL" level="MAJOR" enabled="false" />
+ <coding_rule class="CAA_COVARIANT_ARRAY_RETURN" level="MAJOR" enabled="false" />
+ <coding_rule class="CD_CIRCULAR_DEPENDENCY" level="MAJOR" enabled="false" />
+ <coding_rule class="CI_CONFUSED_INHERITANCE" level="MINOR" enabled="true" />
+ <coding_rule class="CNT_ROUGH_CONSTANT_VALUE" level="MAJOR" enabled="false" />
+ <coding_rule class="CN_IDIOM" level="MAJOR" enabled="true" />
+ <coding_rule class="CN_IDIOM_NO_SUPER_CALL" level="MAJOR" enabled="true" />
+ <coding_rule class="CN_IMPLEMENTS_CLONE_BUT_NOT_CLONEABLE" level="MAJOR" enabled="true" />
+ <coding_rule class="CO_ABSTRACT_SELF" level="MAJOR" enabled="true" />
+ <coding_rule class="CO_COMPARETO_INCORRECT_FLOATING" level="MAJOR" enabled="false" />
+ <coding_rule class="CO_COMPARETO_RESULTS_MIN_VALUE" level="CRITICAL" enabled="false" />
+ <coding_rule class="CO_SELF_NO_OBJECT" level="MAJOR" enabled="true" />
+ <coding_rule class="DB_DUPLICATE_BRANCHES" level="CRITICAL" enabled="true" />
+ <coding_rule class="DB_DUPLICATE_SWITCH_CLAUSES" level="CRITICAL" enabled="true" />
+ <coding_rule class="DC_DOUBLECHECK" level="MAJOR" enabled="true" />
+ <coding_rule class="DC_PARTIALLY_CONSTRUCTED" level="MAJOR" enabled="false" />
+ <coding_rule class="DE_MIGHT_DROP" level="MAJOR" enabled="true" />
+ <coding_rule class="DE_MIGHT_IGNORE" level="MAJOR" enabled="true" />
+ <coding_rule class="DLS_DEAD_LOCAL_INCREMENT_IN_RETURN" level="MAJOR" enabled="false" />
+ <coding_rule class="DLS_DEAD_LOCAL_STORE" level="CRITICAL" enabled="true" />
+ <coding_rule class="DLS_DEAD_LOCAL_STORE_IN_RETURN" level="CRITICAL" enabled="true" />
+ <coding_rule class="DLS_DEAD_LOCAL_STORE_OF_NULL" level="CRITICAL" enabled="true" />
+ <coding_rule class="DLS_DEAD_LOCAL_STORE_SHADOWS_FIELD" level="CRITICAL" enabled="false" />
+ <coding_rule class="DLS_DEAD_STORE_OF_CLASS_LITERAL" level="CRITICAL" enabled="true" />
+ <coding_rule class="DLS_OVERWRITTEN_INCREMENT" level="CRITICAL" enabled="true" />
+ <coding_rule class="DL_SYNCHRONIZATION_ON_BOOLEAN" level="CRITICAL" enabled="true" />
+ <coding_rule class="DL_SYNCHRONIZATION_ON_BOXED_PRIMITIVE" level="CRITICAL" enabled="true" />
+ <coding_rule class="DL_SYNCHRONIZATION_ON_SHARED_CONSTANT" level="CRITICAL" enabled="true" />
+ <coding_rule class="DL_SYNCHRONIZATION_ON_UNSHARED_BOXED_PRIMITIVE" level="CRITICAL" enabled="true" />
+ <coding_rule class="DMI_ANNOTATION_IS_NOT_VISIBLE_TO_REFLECTION" level="MAJOR" enabled="true" />
+ <coding_rule class="DMI_ARGUMENTS_WRONG_ORDER" level="CRITICAL" enabled="false" />
+ <coding_rule class="DMI_BAD_MONTH" level="CRITICAL" enabled="true" />
+ <coding_rule class="DMI_BIGDECIMAL_CONSTRUCTED_FROM_DOUBLE" level="CRITICAL" enabled="false" />
+ <coding_rule class="DMI_BLOCKING_METHODS_ON_URL" level="BLOCKER" enabled="true" />
+ <coding_rule class="DMI_CALLING_NEXT_FROM_HASNEXT" level="CRITICAL" enabled="true" />
+ <coding_rule class="DMI_COLLECTIONS_SHOULD_NOT_CONTAIN_THEMSELVES" level="CRITICAL" enabled="true" />
+ <coding_rule class="DMI_COLLECTION_OF_URLS" level="BLOCKER" enabled="true" />
+ <coding_rule class="DMI_CONSTANT_DB_PASSWORD" level="BLOCKER" enabled="true" />
+ <coding_rule class="DMI_DOH" level="CRITICAL" enabled="false" />
+ <coding_rule class="DMI_EMPTY_DB_PASSWORD" level="CRITICAL" enabled="true" />
+ <coding_rule class="DMI_ENTRY_SETS_MAY_REUSE_ENTRY_OBJECTS" level="CRITICAL" enabled="false" />
+ <coding_rule class="DMI_FUTILE_ATTEMPT_TO_CHANGE_MAXPOOL_SIZE_OF_SCHEDULED_THREAD_POOL_EXECUTOR" level="MINOR" enabled="true" />
+ <coding_rule class="DMI_HARDCODED_ABSOLUTE_FILENAME" level="CRITICAL" enabled="true" />
+ <coding_rule class="DMI_INVOKING_HASHCODE_ON_ARRAY" level="CRITICAL" enabled="true" />
+ <coding_rule class="DMI_INVOKING_TOSTRING_ON_ANONYMOUS_ARRAY" level="CRITICAL" enabled="true" />
+ <coding_rule class="DMI_INVOKING_TOSTRING_ON_ARRAY" level="CRITICAL" enabled="true" />
+ <coding_rule class="DMI_LONG_BITS_TO_DOUBLE_INVOKED_ON_INT" level="CRITICAL" enabled="true" />
+ <coding_rule class="DMI_NONSERIALIZABLE_OBJECT_WRITTEN" level="CRITICAL" enabled="true" />
+ <coding_rule class="DMI_RANDOM_USED_ONLY_ONCE" level="CRITICAL" enabled="true" />
+ <coding_rule class="DMI_SCHEDULED_THREAD_POOL_EXECUTOR_WITH_ZERO_CORE_THREADS" level="MINOR" enabled="true" />
+ <coding_rule class="DMI_THREAD_PASSED_WHERE_RUNNABLE_EXPECTED" level="MAJOR" enabled="true" />
+ <coding_rule class="DMI_UNSUPPORTED_METHOD" level="MAJOR" enabled="true" />
+ <coding_rule class="DMI_USELESS_SUBSTRING" level="CRITICAL" enabled="true" />
+ <coding_rule class="DMI_USING_REMOVEALL_TO_CLEAR_COLLECTION" level="CRITICAL" enabled="true" />
+ <coding_rule class="DMI_VACUOUS_CALL_TO_EASYMOCK_METHOD" level="MINOR" enabled="true" />
+ <coding_rule class="DMI_VACUOUS_SELF_COLLECTION_CALL" level="CRITICAL" enabled="true" />
+ <coding_rule class="DM_BOOLEAN_CTOR" level="MAJOR" enabled="true" />
+ <coding_rule class="DM_BOXED_PRIMITIVE_FOR_COMPARE" level="MAJOR" enabled="false" />
+ <coding_rule class="DM_BOXED_PRIMITIVE_FOR_PARSING" level="MAJOR" enabled="false" />
+ <coding_rule class="DM_BOXED_PRIMITIVE_TOSTRING" level="MAJOR" enabled="true" />
+ <coding_rule class="DM_CONVERT_CASE" level="INFO" enabled="true" />
+ <coding_rule class="DM_DEFAULT_ENCODING" level="CRITICAL" enabled="false" />
+ <coding_rule class="DM_EXIT" level="MAJOR" enabled="true" />
+ <coding_rule class="DM_FP_NUMBER_CTOR" level="MAJOR" enabled="true" />
+ <coding_rule class="DM_GC" level="MAJOR" enabled="true" />
+ <coding_rule class="DM_INVALID_MIN_MAX" level="MAJOR" enabled="false" />
+ <coding_rule class="DM_MONITOR_WAIT_ON_CONDITION" level="MAJOR" enabled="true" />
+ <coding_rule class="DM_NEW_FOR_GETCLASS" level="MAJOR" enabled="true" />
+ <coding_rule class="DM_NEXTINT_VIA_NEXTDOUBLE" level="MAJOR" enabled="true" />
+ <coding_rule class="DM_NUMBER_CTOR" level="CRITICAL" enabled="true" />
+ <coding_rule class="DM_RUN_FINALIZERS_ON_EXIT" level="MAJOR" enabled="true" />
+ <coding_rule class="DM_STRING_CTOR" level="MAJOR" enabled="true" />
+ <coding_rule class="DM_STRING_TOSTRING" level="INFO" enabled="true" />
+ <coding_rule class="DM_STRING_VOID_CTOR" level="MAJOR" enabled="true" />
+ <coding_rule class="DM_USELESS_THREAD" level="MAJOR" enabled="true" />
+ <coding_rule class="DP_CREATE_CLASSLOADER_INSIDE_DO_PRIVILEGED" level="MAJOR" enabled="true" />
+ <coding_rule class="DP_DO_INSIDE_DO_PRIVILEGED" level="MAJOR" enabled="true" />
+ <coding_rule class="EC_ARRAY_AND_NONARRAY" level="CRITICAL" enabled="true" />
+ <coding_rule class="EC_BAD_ARRAY_COMPARE" level="CRITICAL" enabled="true" />
+ <coding_rule class="EC_INCOMPATIBLE_ARRAY_COMPARE" level="MAJOR" enabled="false" />
+ <coding_rule class="EC_NULL_ARG" level="CRITICAL" enabled="true" />
+ <coding_rule class="EC_UNRELATED_CLASS_AND_INTERFACE" level="CRITICAL" enabled="true" />
+ <coding_rule class="EC_UNRELATED_INTERFACES" level="CRITICAL" enabled="true" />
+ <coding_rule class="EC_UNRELATED_TYPES" level="CRITICAL" enabled="true" />
+ <coding_rule class="EC_UNRELATED_TYPES_USING_POINTER_EQUALITY" level="CRITICAL" enabled="true" />
+ <coding_rule class="EI_EXPOSE_REP" level="MAJOR" enabled="true" />
+ <coding_rule class="EI_EXPOSE_REP2" level="MAJOR" enabled="true" />
+ <coding_rule class="EI_EXPOSE_STATIC_REP2" level="MAJOR" enabled="true" />
+ <coding_rule class="EQ_ABSTRACT_SELF" level="MAJOR" enabled="true" />
+ <coding_rule class="EQ_ALWAYS_FALSE" level="BLOCKER" enabled="true" />
+ <coding_rule class="EQ_ALWAYS_TRUE" level="BLOCKER" enabled="true" />
+ <coding_rule class="EQ_CHECK_FOR_OPERAND_NOT_COMPATIBLE_WITH_THIS" level="MAJOR" enabled="true" />
+ <coding_rule class="EQ_COMPARETO_USE_OBJECT_EQUALS" level="CRITICAL" enabled="true" />
+ <coding_rule class="EQ_COMPARING_CLASS_NAMES" level="MAJOR" enabled="true" />
+ <coding_rule class="EQ_DOESNT_OVERRIDE_EQUALS" level="MAJOR" enabled="false" />
+ <coding_rule class="EQ_DONT_DEFINE_EQUALS_FOR_ENUM" level="MAJOR" enabled="true" />
+ <coding_rule class="EQ_GETCLASS_AND_CLASS_CONSTANT" level="CRITICAL" enabled="true" />
+ <coding_rule class="EQ_OTHER_NO_OBJECT" level="MAJOR" enabled="true" />
+ <coding_rule class="EQ_OTHER_USE_OBJECT" level="MAJOR" enabled="true" />
+ <coding_rule class="EQ_OVERRIDING_EQUALS_NOT_SYMMETRIC" level="MAJOR" enabled="true" />
+ <coding_rule class="EQ_SELF_NO_OBJECT" level="MAJOR" enabled="true" />
+ <coding_rule class="EQ_SELF_USE_OBJECT" level="MAJOR" enabled="true" />
+ <coding_rule class="EQ_UNUSUAL" level="MINOR" enabled="true" />
+ <coding_rule class="ES_COMPARING_PARAMETER_STRING_WITH_EQ" level="MAJOR" enabled="true" />
+ <coding_rule class="ES_COMPARING_STRINGS_WITH_EQ" level="MAJOR" enabled="true" />
+ <coding_rule class="ESync_EMPTY_SYNC" level="MAJOR" enabled="true" />
+ <coding_rule class="FB_MISSING_EXPECTED_WARNING" level="CRITICAL" enabled="false" />
+ <coding_rule class="FB_UNEXPECTED_WARNING" level="CRITICAL" enabled="false" />
+ <coding_rule class="FE_FLOATING_POINT_EQUALITY" level="CRITICAL" enabled="true" />
+ <coding_rule class="FE_TEST_IF_EQUAL_TO_NOT_A_NUMBER" level="CRITICAL" enabled="true" />
+ <coding_rule class="FI_EMPTY" level="MAJOR" enabled="true" />
+ <coding_rule class="FI_EXPLICIT_INVOCATION" level="MAJOR" enabled="true" />
+ <coding_rule class="FI_FINALIZER_NULLS_FIELDS" level="MAJOR" enabled="true" />
+ <coding_rule class="FI_FINALIZER_ONLY_NULLS_FIELDS" level="MAJOR" enabled="true" />
+ <coding_rule class="FI_MISSING_SUPER_CALL" level="MAJOR" enabled="true" />
+ <coding_rule class="FI_NULLIFY_SUPER" level="CRITICAL" enabled="true" />
+ <coding_rule class="FI_PUBLIC_SHOULD_BE_PROTECTED" level="MAJOR" enabled="true" />
+ <coding_rule class="FI_USELESS" level="MINOR" enabled="true" />
+ <coding_rule class="FL_MATH_USING_FLOAT_PRECISION" level="CRITICAL" enabled="true" />
+ <coding_rule class="GC_UNCHECKED_TYPE_IN_GENERIC_CALL" level="CRITICAL" enabled="true" />
+ <coding_rule class="GC_UNRELATED_TYPES" level="CRITICAL" enabled="true" />
+ <coding_rule class="HE_EQUALS_NO_HASHCODE" level="MAJOR" enabled="true" />
+ <coding_rule class="HE_EQUALS_USE_HASHCODE" level="CRITICAL" enabled="true" />
+ <coding_rule class="HE_HASHCODE_NO_EQUALS" level="CRITICAL" enabled="true" />
+ <coding_rule class="HE_HASHCODE_USE_OBJECT_EQUALS" level="CRITICAL" enabled="true" />
+ <coding_rule class="HE_INHERITS_EQUALS_USE_HASHCODE" level="CRITICAL" enabled="true" />
+ <coding_rule class="HE_SIGNATURE_DECLARES_HASHING_OF_UNHASHABLE_CLASS" level="CRITICAL" enabled="true" />
+ <coding_rule class="HE_USE_OF_UNHASHABLE_CLASS" level="CRITICAL" enabled="true" />
+ <coding_rule class="HRS_REQUEST_PARAMETER_TO_COOKIE" level="MAJOR" enabled="true" />
+ <coding_rule class="HRS_REQUEST_PARAMETER_TO_HTTP_HEADER" level="MAJOR" enabled="true" />
+ <coding_rule class="HSC_HUGE_SHARED_STRING_CONSTANT" level="CRITICAL" enabled="true" />
+ <coding_rule class="IA_AMBIGUOUS_INVOCATION_OF_INHERITED_OR_OUTER_METHOD" level="MAJOR" enabled="true" />
+ <coding_rule class="ICAST_BAD_SHIFT_AMOUNT" level="CRITICAL" enabled="true" />
+ <coding_rule class="ICAST_IDIV_CAST_TO_DOUBLE" level="CRITICAL" enabled="true" />
+ <coding_rule class="ICAST_INTEGER_MULTIPLY_CAST_TO_LONG" level="CRITICAL" enabled="true" />
+ <coding_rule class="ICAST_INT_2_LONG_AS_INSTANT" level="CRITICAL" enabled="false" />
+ <coding_rule class="ICAST_INT_CAST_TO_DOUBLE_PASSED_TO_CEIL" level="CRITICAL" enabled="true" />
+ <coding_rule class="ICAST_INT_CAST_TO_FLOAT_PASSED_TO_ROUND" level="CRITICAL" enabled="true" />
+ <coding_rule class="ICAST_QUESTIONABLE_UNSIGNED_RIGHT_SHIFT" level="CRITICAL" enabled="true" />
+ <coding_rule class="IC_INIT_CIRCULARITY" level="CRITICAL" enabled="true" />
+ <coding_rule class="IC_SUPERCLASS_USES_SUBCLASS_DURING_INITIALIZATION" level="MAJOR" enabled="true" />
+ <coding_rule class="IIL_ELEMENTS_GET_LENGTH_IN_LOOP" level="MAJOR" enabled="false" />
+ <coding_rule class="IIL_PATTERN_COMPILE_IN_LOOP" level="MAJOR" enabled="false" />
+ <coding_rule class="IIL_PATTERN_COMPILE_IN_LOOP_INDIRECT" level="MAJOR" enabled="false" />
+ <coding_rule class="IIL_PREPARE_STATEMENT_IN_LOOP" level="MAJOR" enabled="false" />
+ <coding_rule class="IIO_INEFFICIENT_INDEX_OF" level="MAJOR" enabled="false" />
+ <coding_rule class="IIO_INEFFICIENT_LAST_INDEX_OF" level="MAJOR" enabled="false" />
+ <coding_rule class="IJU_ASSERT_METHOD_INVOKED_FROM_RUN_METHOD" level="CRITICAL" enabled="true" />
+ <coding_rule class="IJU_BAD_SUITE_METHOD" level="CRITICAL" enabled="true" />
+ <coding_rule class="IJU_NO_TESTS" level="CRITICAL" enabled="true" />
+ <coding_rule class="IJU_SETUP_NO_SUPER" level="CRITICAL" enabled="true" />
+ <coding_rule class="IJU_SUITE_NOT_STATIC" level="CRITICAL" enabled="true" />
+ <coding_rule class="IJU_TEARDOWN_NO_SUPER" level="CRITICAL" enabled="true" />
+ <coding_rule class="IL_CONTAINER_ADDED_TO_ITSELF" level="CRITICAL" enabled="true" />
+ <coding_rule class="IL_INFINITE_LOOP" level="CRITICAL" enabled="true" />
+ <coding_rule class="IL_INFINITE_RECURSIVE_LOOP" level="CRITICAL" enabled="true" />
+ <coding_rule class="IMA_INEFFICIENT_MEMBER_ACCESS" level="MAJOR" enabled="false" />
+ <coding_rule class="IMSE_DONT_CATCH_IMSE" level="MAJOR" enabled="true" />
+ <coding_rule class="IM_AVERAGE_COMPUTATION_COULD_OVERFLOW" level="CRITICAL" enabled="true" />
+ <coding_rule class="IM_BAD_CHECK_FOR_ODD" level="CRITICAL" enabled="true" />
+ <coding_rule class="IM_MULTIPLYING_RESULT_OF_IREM" level="CRITICAL" enabled="true" />
+ <coding_rule class="INT_BAD_COMPARISON_WITH_INT_VALUE" level="CRITICAL" enabled="false" />
+ <coding_rule class="INT_BAD_COMPARISON_WITH_NONNEGATIVE_VALUE" level="CRITICAL" enabled="true" />
+ <coding_rule class="INT_BAD_COMPARISON_WITH_SIGNED_BYTE" level="CRITICAL" enabled="true" />
+ <coding_rule class="INT_BAD_REM_BY_1" level="CRITICAL" enabled="true" />
+ <coding_rule class="INT_VACUOUS_BIT_OPERATION" level="CRITICAL" enabled="true" />
+ <coding_rule class="INT_VACUOUS_COMPARISON" level="CRITICAL" enabled="true" />
+ <coding_rule class="IO_APPENDING_TO_OBJECT_OUTPUT_STREAM" level="CRITICAL" enabled="true" />
+ <coding_rule class="IP_PARAMETER_IS_DEAD_BUT_OVERWRITTEN" level="CRITICAL" enabled="true" />
+ <coding_rule class="IS2_INCONSISTENT_SYNC" level="CRITICAL" enabled="true" />
+ <coding_rule class="ISC_INSTANTIATE_STATIC_CLASS" level="MAJOR" enabled="true" />
+ <coding_rule class="IS_FIELD_NOT_GUARDED" level="CRITICAL" enabled="true" />
+ <coding_rule class="ITA_INEFFICIENT_TO_ARRAY" level="CRITICAL" enabled="true" />
+ <coding_rule class="IT_NO_SUCH_ELEMENT" level="MINOR" enabled="true" />
+ <coding_rule class="J2EE_STORE_OF_NON_SERIALIZABLE_OBJECT_INTO_SESSION" level="CRITICAL" enabled="true" />
+ <coding_rule class="JCIP_FIELD_ISNT_FINAL_IN_IMMUTABLE_CLASS" level="MINOR" enabled="true" />
+ <coding_rule class="JLM_JSR166_LOCK_MONITORENTER" level="CRITICAL" enabled="true" />
+ <coding_rule class="JLM_JSR166_UTILCONCURRENT_MONITORENTER" level="CRITICAL" enabled="false" />
+ <coding_rule class="JML_JSR166_CALLING_WAIT_RATHER_THAN_AWAIT" level="CRITICAL" enabled="false" />
+ <coding_rule class="LG_LOST_LOGGER_DUE_TO_WEAK_REFERENCE" level="MAJOR" enabled="false" />
+ <coding_rule class="LI_LAZY_INIT_STATIC" level="CRITICAL" enabled="true" />
+ <coding_rule class="LI_LAZY_INIT_UPDATE_STATIC" level="CRITICAL" enabled="true" />
+ <coding_rule class="ME_ENUM_FIELD_SETTER" level="MAJOR" enabled="false" />
+ <coding_rule class="ME_MUTABLE_ENUM_FIELD" level="MAJOR" enabled="false" />
+ <coding_rule class="MF_CLASS_MASKS_FIELD" level="MAJOR" enabled="true" />
+ <coding_rule class="MF_METHOD_MASKS_FIELD" level="MAJOR" enabled="true" />
+ <coding_rule class="ML_SYNC_ON_FIELD_TO_GUARD_CHANGING_THAT_FIELD" level="MAJOR" enabled="true" />
+ <coding_rule class="ML_SYNC_ON_UPDATED_FIELD" level="MAJOR" enabled="true" />
+ <coding_rule class="MSF_MUTABLE_SERVLET_FIELD" level="MAJOR" enabled="true" />
+ <coding_rule class="MS_CANNOT_BE_FINAL" level="MAJOR" enabled="true" />
+ <coding_rule class="MS_EXPOSE_REP" level="CRITICAL" enabled="true" />
+ <coding_rule class="MS_FINAL_PKGPROTECT" level="MAJOR" enabled="true" />
+ <coding_rule class="MS_MUTABLE_ARRAY" level="MAJOR" enabled="true" />
+ <coding_rule class="MS_MUTABLE_COLLECTION" level="MAJOR" enabled="false" />
+ <coding_rule class="MS_MUTABLE_COLLECTION_PKGPROTECT" level="MAJOR" enabled="false" />
+ <coding_rule class="MS_MUTABLE_HASHTABLE" level="MAJOR" enabled="true" />
+ <coding_rule class="MS_OOI_PKGPROTECT" level="MAJOR" enabled="true" />
+ <coding_rule class="MS_PKGPROTECT" level="MAJOR" enabled="true" />
+ <coding_rule class="MS_SHOULD_BE_FINAL" level="MAJOR" enabled="true" />
+ <coding_rule class="MS_SHOULD_BE_REFACTORED_TO_BE_FINAL" level="CRITICAL" enabled="false" />
+ <coding_rule class="MTIA_SUSPECT_SERVLET_INSTANCE_FIELD" level="CRITICAL" enabled="true" />
+ <coding_rule class="MTIA_SUSPECT_STRUTS_INSTANCE_FIELD" level="CRITICAL" enabled="true" />
+ <coding_rule class="MWN_MISMATCHED_NOTIFY" level="CRITICAL" enabled="true" />
+ <coding_rule class="MWN_MISMATCHED_WAIT" level="CRITICAL" enabled="true" />
+ <coding_rule class="NM_BAD_EQUAL" level="MAJOR" enabled="false" />
+ <coding_rule class="NM_CLASS_NAMING_CONVENTION" level="MAJOR" enabled="false" />
+ <coding_rule class="NM_CLASS_NOT_EXCEPTION" level="MAJOR" enabled="true" />
+ <coding_rule class="NM_CONFUSING" level="MAJOR" enabled="true" />
+ <coding_rule class="NM_FIELD_NAMING_CONVENTION" level="MAJOR" enabled="false" />
+ <coding_rule class="NM_FUTURE_KEYWORD_USED_AS_IDENTIFIER" level="MAJOR" enabled="true" />
+ <coding_rule class="NM_FUTURE_KEYWORD_USED_AS_MEMBER_IDENTIFIER" level="MAJOR" enabled="true" />
+ <coding_rule class="NM_LCASE_HASHCODE" level="MAJOR" enabled="false" />
+ <coding_rule class="NM_LCASE_TOSTRING" level="MAJOR" enabled="false" />
+ <coding_rule class="NM_METHOD_CONSTRUCTOR_CONFUSION" level="MAJOR" enabled="true" />
+ <coding_rule class="NM_METHOD_NAMING_CONVENTION" level="MAJOR" enabled="false" />
+ <coding_rule class="NM_SAME_SIMPLE_NAME_AS_INTERFACE" level="MAJOR" enabled="true" />
+ <coding_rule class="NM_SAME_SIMPLE_NAME_AS_SUPERCLASS" level="MAJOR" enabled="true" />
+ <coding_rule class="NM_VERY_CONFUSING" level="MAJOR" enabled="true" />
+ <coding_rule class="NM_VERY_CONFUSING_INTENTIONAL" level="MAJOR" enabled="true" />
+ <coding_rule class="NM_WRONG_PACKAGE" level="MAJOR" enabled="true" />
+ <coding_rule class="NM_WRONG_PACKAGE_INTENTIONAL" level="MAJOR" enabled="true" />
+ <coding_rule class="NN_NAKED_NOTIFY" level="CRITICAL" enabled="true" />
+ <coding_rule class="NOISE_FIELD_REFERENCE" level="MAJOR" enabled="false" />
+ <coding_rule class="NOISE_METHOD_CALL" level="MAJOR" enabled="false" />
+ <coding_rule class="NOISE_NULL_DEREFERENCE" level="MAJOR" enabled="false" />
+ <coding_rule class="NOISE_OPERATION" level="MAJOR" enabled="false" />
+ <coding_rule class="NO_NOTIFY_NOT_NOTIFYALL" level="CRITICAL" enabled="true" />
+ <coding_rule class="NP_ALWAYS_NULL" level="CRITICAL" enabled="true" />
+ <coding_rule class="NP_ALWAYS_NULL_EXCEPTION" level="CRITICAL" enabled="true" />
+ <coding_rule class="NP_ARGUMENT_MIGHT_BE_NULL" level="MAJOR" enabled="true" />
+ <coding_rule class="NP_BOOLEAN_RETURN_NULL" level="MAJOR" enabled="true" />
+ <coding_rule class="NP_CLONE_COULD_RETURN_NULL" level="CRITICAL" enabled="true" />
+ <coding_rule class="NP_CLOSING_NULL" level="MAJOR" enabled="false" />
+ <coding_rule class="NP_DEREFERENCE_OF_READLINE_VALUE" level="CRITICAL" enabled="true" />
+ <coding_rule class="NP_EQUALS_SHOULD_HANDLE_NULL_ARGUMENT" level="CRITICAL" enabled="true" />
+ <coding_rule class="NP_GUARANTEED_DEREF" level="BLOCKER" enabled="true" />
+ <coding_rule class="NP_GUARANTEED_DEREF_ON_EXCEPTION_PATH" level="CRITICAL" enabled="true" />
+ <coding_rule class="NP_IMMEDIATE_DEREFERENCE_OF_READLINE" level="CRITICAL" enabled="true" />
+ <coding_rule class="NP_LOAD_OF_KNOWN_NULL_VALUE" level="CRITICAL" enabled="true" />
+ <coding_rule class="NP_METHOD_PARAMETER_TIGHTENS_ANNOTATION" level="MAJOR" enabled="false" />
+ <coding_rule class="NP_METHOD_RETURN_RELAXING_ANNOTATION" level="MAJOR" enabled="false" />
+ <coding_rule class="NP_NONNULL_FIELD_NOT_INITIALIZED_IN_CONSTRUCTOR" level="CRITICAL" enabled="false" />
+ <coding_rule class="NP_NONNULL_PARAM_VIOLATION" level="CRITICAL" enabled="true" />
+ <coding_rule class="NP_NONNULL_RETURN_VIOLATION" level="CRITICAL" enabled="true" />
+ <coding_rule class="NP_NULL_INSTANCEOF" level="BLOCKER" enabled="true" />
+ <coding_rule class="NP_NULL_ON_SOME_PATH" level="CRITICAL" enabled="true" />
+ <coding_rule class="NP_NULL_ON_SOME_PATH_EXCEPTION" level="CRITICAL" enabled="true" />
+ <coding_rule class="NP_NULL_ON_SOME_PATH_FROM_RETURN_VALUE" level="CRITICAL" enabled="true" />
+ <coding_rule class="NP_NULL_ON_SOME_PATH_MIGHT_BE_INFEASIBLE" level="CRITICAL" enabled="true" />
+ <coding_rule class="NP_NULL_PARAM_DEREF" level="CRITICAL" enabled="true" />
+ <coding_rule class="NP_NULL_PARAM_DEREF_ALL_TARGETS_DANGEROUS" level="CRITICAL" enabled="true" />
+ <coding_rule class="NP_NULL_PARAM_DEREF_NONVIRTUAL" level="CRITICAL" enabled="true" />
+ <coding_rule class="NP_OPTIONAL_RETURN_NULL" level="MAJOR" enabled="false" />
+ <coding_rule class="NP_PARAMETER_MUST_BE_NONNULL_BUT_MARKED_AS_NULLABLE" level="CRITICAL" enabled="true" />
+ <coding_rule class="NP_STORE_INTO_NONNULL_FIELD" level="CRITICAL" enabled="true" />
+ <coding_rule class="NP_SYNC_AND_NULL_CHECK_FIELD" level="MAJOR" enabled="true" />
+ <coding_rule class="NP_TOSTRING_COULD_RETURN_NULL" level="CRITICAL" enabled="true" />
+ <coding_rule class="NP_UNWRITTEN_FIELD" level="MAJOR" enabled="false" />
+ <coding_rule class="NP_UNWRITTEN_PUBLIC_OR_PROTECTED_FIELD" level="CRITICAL" enabled="false" />
+ <coding_rule class="NS_DANGEROUS_NON_SHORT_CIRCUIT" level="CRITICAL" enabled="true" />
+ <coding_rule class="NS_NON_SHORT_CIRCUIT" level="MAJOR" enabled="true" />
+ <coding_rule class="OBL_UNSATISFIED_OBLIGATION" level="CRITICAL" enabled="false" />
+ <coding_rule class="OBL_UNSATISFIED_OBLIGATION_EXCEPTION_EDGE" level="CRITICAL" enabled="false" />
+ <coding_rule class="ODR_OPEN_DATABASE_RESOURCE" level="CRITICAL" enabled="true" />
+ <coding_rule class="ODR_OPEN_DATABASE_RESOURCE_EXCEPTION_PATH" level="CRITICAL" enabled="true" />
+ <coding_rule class="OS_OPEN_STREAM" level="CRITICAL" enabled="true" />
+ <coding_rule class="OS_OPEN_STREAM_EXCEPTION_PATH" level="CRITICAL" enabled="true" />
+ <coding_rule class="PS_PUBLIC_SEMAPHORES" level="CRITICAL" enabled="true" />
+ <coding_rule class="PT_ABSOLUTE_PATH_TRAVERSAL" level="CRITICAL" enabled="false" />
+ <coding_rule class="PT_RELATIVE_PATH_TRAVERSAL" level="CRITICAL" enabled="false" />
+ <coding_rule class="PZLA_PREFER_ZERO_LENGTH_ARRAYS" level="MAJOR" enabled="true" />
+ <coding_rule class="PZ_DONT_REUSE_ENTRY_OBJECTS_IN_ITERATORS" level="CRITICAL" enabled="false" />
+ <coding_rule class="QBA_QUESTIONABLE_BOOLEAN_ASSIGNMENT" level="CRITICAL" enabled="true" />
+ <coding_rule class="QF_QUESTIONABLE_FOR_LOOP" level="CRITICAL" enabled="true" />
+ <coding_rule class="RANGE_ARRAY_INDEX" level="MAJOR" enabled="false" />
+ <coding_rule class="RANGE_ARRAY_LENGTH" level="MAJOR" enabled="false" />
+ <coding_rule class="RANGE_ARRAY_OFFSET" level="MAJOR" enabled="false" />
+ <coding_rule class="RANGE_STRING_INDEX" level="MAJOR" enabled="false" />
+ <coding_rule class="RCN_REDUNDANT_COMPARISON_OF_NULL_AND_NONNULL_VALUE" level="CRITICAL" enabled="true" />
+ <coding_rule class="RCN_REDUNDANT_COMPARISON_TWO_NULL_VALUES" level="CRITICAL" enabled="true" />
+ <coding_rule class="RCN_REDUNDANT_NULLCHECK_OF_NONNULL_VALUE" level="CRITICAL" enabled="true" />
+ <coding_rule class="RCN_REDUNDANT_NULLCHECK_OF_NULL_VALUE" level="CRITICAL" enabled="true" />
+ <coding_rule class="RCN_REDUNDANT_NULLCHECK_WOULD_HAVE_BEEN_A_NPE" level="CRITICAL" enabled="true" />
+ <coding_rule class="RC_REF_COMPARISON" level="CRITICAL" enabled="true" />
+ <coding_rule class="RC_REF_COMPARISON_BAD_PRACTICE" level="MAJOR" enabled="false" />
+ <coding_rule class="RC_REF_COMPARISON_BAD_PRACTICE_BOOLEAN" level="MAJOR" enabled="false" />
+ <coding_rule class="REC_CATCH_EXCEPTION" level="MAJOR" enabled="true" />
+ <coding_rule class="RE_BAD_SYNTAX_FOR_REGULAR_EXPRESSION" level="CRITICAL" enabled="true" />
+ <coding_rule class="RE_CANT_USE_FILE_SEPARATOR_AS_REGULAR_EXPRESSION" level="CRITICAL" enabled="true" />
+ <coding_rule class="RE_POSSIBLE_UNINTENDED_PATTERN" level="CRITICAL" enabled="true" />
+ <coding_rule class="RI_REDUNDANT_INTERFACES" level="MAJOR" enabled="true" />
+ <coding_rule class="RR_NOT_CHECKED" level="MAJOR" enabled="true" />
+ <coding_rule class="RS_READOBJECT_SYNC" level="CRITICAL" enabled="true" />
+ <coding_rule class="RU_INVOKE_RUN" level="MAJOR" enabled="true" />
+ <coding_rule class="RV_01_TO_INT" level="MAJOR" enabled="true" />
+ <coding_rule class="RV_ABSOLUTE_VALUE_OF_HASHCODE" level="CRITICAL" enabled="true" />
+ <coding_rule class="RV_ABSOLUTE_VALUE_OF_RANDOM_INT" level="CRITICAL" enabled="true" />
+ <coding_rule class="RV_CHECK_COMPARETO_FOR_SPECIFIC_RETURN_VALUE" level="CRITICAL" enabled="false" />
+ <coding_rule class="RV_CHECK_FOR_POSITIVE_INDEXOF" level="MINOR" enabled="true" />
+ <coding_rule class="RV_DONT_JUST_NULL_CHECK_READLINE" level="MAJOR" enabled="true" />
+ <coding_rule class="RV_EXCEPTION_NOT_THROWN" level="CRITICAL" enabled="true" />
+ <coding_rule class="RV_NEGATING_RESULT_OF_COMPARETO" level="CRITICAL" enabled="false" />
+ <coding_rule class="RV_REM_OF_HASHCODE" level="CRITICAL" enabled="true" />
+ <coding_rule class="RV_REM_OF_RANDOM_INT" level="CRITICAL" enabled="true" />
+ <coding_rule class="RV_RETURN_VALUE_IGNORED" level="MINOR" enabled="true" />
+ <coding_rule class="RV_RETURN_VALUE_IGNORED_BAD_PRACTICE" level="MAJOR" enabled="true" />
+ <coding_rule class="RV_RETURN_VALUE_IGNORED_INFERRED" level="CRITICAL" enabled="false" />
+ <coding_rule class="RV_RETURN_VALUE_IGNORED_NO_SIDE_EFFECT" level="MAJOR" enabled="false" />
+ <coding_rule class="RV_RETURN_VALUE_OF_PUTIFABSENT_IGNORED" level="MAJOR" enabled="false" />
+ <coding_rule class="RpC_REPEATED_CONDITIONAL_TEST" level="MAJOR" enabled="true" />
+ <coding_rule class="SA_FIELD_DOUBLE_ASSIGNMENT" level="CRITICAL" enabled="true" />
+ <coding_rule class="SA_FIELD_SELF_ASSIGNMENT" level="CRITICAL" enabled="true" />
+ <coding_rule class="SA_FIELD_SELF_COMPARISON" level="CRITICAL" enabled="true" />
+ <coding_rule class="SA_FIELD_SELF_COMPUTATION" level="CRITICAL" enabled="true" />
+ <coding_rule class="SA_LOCAL_DOUBLE_ASSIGNMENT" level="CRITICAL" enabled="true" />
+ <coding_rule class="SA_LOCAL_SELF_ASSIGNMENT" level="CRITICAL" enabled="true" />
+ <coding_rule class="SA_LOCAL_SELF_ASSIGNMENT_INSTEAD_OF_FIELD" level="CRITICAL" enabled="false" />
+ <coding_rule class="SA_LOCAL_SELF_COMPARISON" level="CRITICAL" enabled="true" />
+ <coding_rule class="SA_LOCAL_SELF_COMPUTATION" level="CRITICAL" enabled="true" />
+ <coding_rule class="SBSC_USE_STRINGBUFFER_CONCATENATION" level="CRITICAL" enabled="true" />
+ <coding_rule class="SC_START_IN_CTOR" level="CRITICAL" enabled="true" />
+ <coding_rule class="SE_BAD_FIELD" level="MINOR" enabled="false" />
+ <coding_rule class="SE_BAD_FIELD_INNER_CLASS" level="MINOR" enabled="true" />
+ <coding_rule class="SE_BAD_FIELD_STORE" level="CRITICAL" enabled="true" />
+ <coding_rule class="SE_COMPARATOR_SHOULD_BE_SERIALIZABLE" level="MAJOR" enabled="true" />
+ <coding_rule class="SE_INNER_CLASS" level="MAJOR" enabled="true" />
+ <coding_rule class="SE_METHOD_MUST_BE_PRIVATE" level="MAJOR" enabled="true" />
+ <coding_rule class="SE_NONFINAL_SERIALVERSIONID" level="CRITICAL" enabled="true" />
+ <coding_rule class="SE_NONLONG_SERIALVERSIONID" level="MAJOR" enabled="true" />
+ <coding_rule class="SE_NONSTATIC_SERIALVERSIONID" level="MAJOR" enabled="true" />
+ <coding_rule class="SE_NO_SERIALVERSIONID" level="MAJOR" enabled="true" />
+ <coding_rule class="SE_NO_SUITABLE_CONSTRUCTOR" level="MAJOR" enabled="true" />
+ <coding_rule class="SE_NO_SUITABLE_CONSTRUCTOR_FOR_EXTERNALIZATION" level="MAJOR" enabled="true" />
+ <coding_rule class="SE_PRIVATE_READ_RESOLVE_NOT_INHERITED" level="MAJOR" enabled="true" />
+ <coding_rule class="SE_READ_RESOLVE_IS_STATIC" level="MAJOR" enabled="true" />
+ <coding_rule class="SE_READ_RESOLVE_MUST_RETURN_OBJECT" level="MAJOR" enabled="true" />
+ <coding_rule class="SE_TRANSIENT_FIELD_NOT_RESTORED" level="MAJOR" enabled="true" />
+ <coding_rule class="SE_TRANSIENT_FIELD_OF_NONSERIALIZABLE_CLASS" level="MAJOR" enabled="true" />
+ <coding_rule class="SF_DEAD_STORE_DUE_TO_SWITCH_FALLTHROUGH" level="MAJOR" enabled="false" />
+ <coding_rule class="SF_DEAD_STORE_DUE_TO_SWITCH_FALLTHROUGH_TO_THROW" level="MAJOR" enabled="false" />
+ <coding_rule class="SF_SWITCH_FALLTHROUGH" level="MAJOR" enabled="false" />
+ <coding_rule class="SF_SWITCH_NO_DEFAULT" level="MAJOR" enabled="false" />
+ <coding_rule class="SIC_INNER_SHOULD_BE_STATIC" level="MAJOR" enabled="true" />
+ <coding_rule class="SIC_INNER_SHOULD_BE_STATIC_ANON" level="MAJOR" enabled="true" />
+ <coding_rule class="SIC_INNER_SHOULD_BE_STATIC_NEEDS_THIS" level="MAJOR" enabled="true" />
+ <coding_rule class="SIC_THREADLOCAL_DEADLY_EMBRACE" level="MAJOR" enabled="false" />
+ <coding_rule class="SIO_SUPERFLUOUS_INSTANCEOF" level="CRITICAL" enabled="true" />
+ <coding_rule class="SI_INSTANCE_BEFORE_FINALS_ASSIGNED" level="CRITICAL" enabled="true" />
+ <coding_rule class="SP_SPIN_ON_FIELD" level="MAJOR" enabled="true" />
+ <coding_rule class="SQL_BAD_PREPARED_STATEMENT_ACCESS" level="CRITICAL" enabled="true" />
+ <coding_rule class="SQL_BAD_RESULTSET_ACCESS" level="CRITICAL" enabled="true" />
+ <coding_rule class="SQL_NONCONSTANT_STRING_PASSED_TO_EXECUTE" level="CRITICAL" enabled="true" />
+ <coding_rule class="SQL_PREPARED_STATEMENT_GENERATED_FROM_NONCONSTANT_STRING" level="CRITICAL" enabled="true" />
+ <coding_rule class="SR_NOT_CHECKED" level="MAJOR" enabled="true" />
+ <coding_rule class="SS_SHOULD_BE_STATIC" level="MAJOR" enabled="true" />
+ <coding_rule class="STCAL_INVOKE_ON_STATIC_CALENDAR_INSTANCE" level="CRITICAL" enabled="true" />
+ <coding_rule class="STCAL_INVOKE_ON_STATIC_DATE_FORMAT_INSTANCE" level="CRITICAL" enabled="true" />
+ <coding_rule class="STCAL_STATIC_CALENDAR_INSTANCE" level="CRITICAL" enabled="true" />
+ <coding_rule class="STCAL_STATIC_SIMPLE_DATE_FORMAT_INSTANCE" level="CRITICAL" enabled="true" />
+ <coding_rule class="STI_INTERRUPTED_ON_CURRENTTHREAD" level="CRITICAL" enabled="true" />
+ <coding_rule class="STI_INTERRUPTED_ON_UNKNOWNTHREAD" level="CRITICAL" enabled="true" />
+ <coding_rule class="ST_WRITE_TO_STATIC_FROM_INSTANCE_METHOD" level="CRITICAL" enabled="true" />
+ <coding_rule class="SWL_SLEEP_WITH_LOCK_HELD" level="CRITICAL" enabled="true" />
+ <coding_rule class="SW_SWING_METHODS_INVOKED_IN_SWING_THREAD" level="MAJOR" enabled="true" />
+ <coding_rule class="TLW_TWO_LOCK_WAIT" level="MAJOR" enabled="true" />
+ <coding_rule class="TQ_ALWAYS_VALUE_USED_WHERE_NEVER_REQUIRED" level="CRITICAL" enabled="true" />
+ <coding_rule class="TQ_COMPARING_VALUES_WITH_INCOMPATIBLE_TYPE_QUALIFIERS" level="CRITICAL" enabled="false" />
+ <coding_rule class="TQ_EXPLICIT_UNKNOWN_SOURCE_VALUE_REACHES_ALWAYS_SINK" level="CRITICAL" enabled="true" />
+ <coding_rule class="TQ_EXPLICIT_UNKNOWN_SOURCE_VALUE_REACHES_NEVER_SINK" level="CRITICAL" enabled="true" />
+ <coding_rule class="TQ_MAYBE_SOURCE_VALUE_REACHES_ALWAYS_SINK" level="CRITICAL" enabled="true" />
+ <coding_rule class="TQ_MAYBE_SOURCE_VALUE_REACHES_NEVER_SINK" level="CRITICAL" enabled="true" />
+ <coding_rule class="TQ_NEVER_VALUE_USED_WHERE_ALWAYS_REQUIRED" level="CRITICAL" enabled="true" />
+ <coding_rule class="TQ_UNKNOWN_VALUE_USED_WHERE_ALWAYS_STRICTLY_REQUIRED" level="CRITICAL" enabled="false" />
+ <coding_rule class="UCF_USELESS_CONTROL_FLOW" level="CRITICAL" enabled="true" />
+ <coding_rule class="UCF_USELESS_CONTROL_FLOW_NEXT_LINE" level="CRITICAL" enabled="true" />
+ <coding_rule class="UC_USELESS_CONDITION" level="MAJOR" enabled="false" />
+ <coding_rule class="UC_USELESS_CONDITION_TYPE" level="MAJOR" enabled="false" />
+ <coding_rule class="UC_USELESS_OBJECT" level="MAJOR" enabled="false" />
+ <coding_rule class="UC_USELESS_OBJECT_STACK" level="MAJOR" enabled="false" />
+ <coding_rule class="UC_USELESS_VOID_METHOD" level="MAJOR" enabled="false" />
+ <coding_rule class="UG_SYNC_SET_UNSYNC_GET" level="MAJOR" enabled="true" />
+ <coding_rule class="UI_INHERITANCE_UNSAFE_GETRESOURCE" level="MAJOR" enabled="true" />
+ <coding_rule class="UL_UNRELEASED_LOCK" level="CRITICAL" enabled="true" />
+ <coding_rule class="UL_UNRELEASED_LOCK_EXCEPTION_PATH" level="CRITICAL" enabled="true" />
+ <coding_rule class="UMAC_UNCALLABLE_METHOD_OF_ANONYMOUS_CLASS" level="CRITICAL" enabled="true" />
+ <coding_rule class="UM_UNNECESSARY_MATH" level="CRITICAL" enabled="true" />
+ <coding_rule class="UPM_UNCALLED_PRIVATE_METHOD" level="CRITICAL" enabled="true" />
+ <coding_rule class="URF_UNREAD_FIELD" level="MAJOR" enabled="true" />
+ <coding_rule class="URF_UNREAD_PUBLIC_OR_PROTECTED_FIELD" level="CRITICAL" enabled="false" />
+ <coding_rule class="UR_UNINIT_READ" level="MAJOR" enabled="true" />
+ <coding_rule class="UR_UNINIT_READ_CALLED_FROM_SUPER_CONSTRUCTOR" level="MAJOR" enabled="false" />
+ <coding_rule class="USM_USELESS_ABSTRACT_METHOD" level="MAJOR" enabled="false" />
+ <coding_rule class="USM_USELESS_SUBCLASS_METHOD" level="MAJOR" enabled="false" />
+ <coding_rule class="UUF_UNUSED_FIELD" level="MAJOR" enabled="true" />
+ <coding_rule class="UUF_UNUSED_PUBLIC_OR_PROTECTED_FIELD" level="CRITICAL" enabled="false" />
+ <coding_rule class="UWF_FIELD_NOT_INITIALIZED_IN_CONSTRUCTOR" level="MAJOR" enabled="false" />
+ <coding_rule class="UWF_NULL_FIELD" level="CRITICAL" enabled="true" />
+ <coding_rule class="UWF_UNWRITTEN_FIELD" level="MAJOR" enabled="false" />
+ <coding_rule class="UWF_UNWRITTEN_PUBLIC_OR_PROTECTED_FIELD" level="CRITICAL" enabled="false" />
+ <coding_rule class="UW_UNCOND_WAIT" level="MAJOR" enabled="true" />
+ <coding_rule class="VA_FORMAT_STRING_BAD_ARGUMENT" level="CRITICAL" enabled="true" />
+ <coding_rule class="VA_FORMAT_STRING_BAD_CONVERSION" level="CRITICAL" enabled="true" />
+ <coding_rule class="VA_FORMAT_STRING_BAD_CONVERSION_FROM_ARRAY" level="MAJOR" enabled="true" />
+ <coding_rule class="VA_FORMAT_STRING_BAD_CONVERSION_TO_BOOLEAN" level="MAJOR" enabled="true" />
+ <coding_rule class="VA_FORMAT_STRING_EXPECTED_MESSAGE_FORMAT_SUPPLIED" level="MAJOR" enabled="false" />
+ <coding_rule class="VA_FORMAT_STRING_EXTRA_ARGUMENTS_PASSED" level="MAJOR" enabled="true" />
+ <coding_rule class="VA_FORMAT_STRING_ILLEGAL" level="CRITICAL" enabled="true" />
+ <coding_rule class="VA_FORMAT_STRING_MISSING_ARGUMENT" level="CRITICAL" enabled="true" />
+ <coding_rule class="VA_FORMAT_STRING_NO_PREVIOUS_ARGUMENT" level="CRITICAL" enabled="true" />
+ <coding_rule class="VA_FORMAT_STRING_USES_NEWLINE" level="CRITICAL" enabled="false" />
+ <coding_rule class="VA_PRIMITIVE_ARRAY_PASSED_TO_OBJECT_VARARG" level="CRITICAL" enabled="true" />
+ <coding_rule class="VO_VOLATILE_INCREMENT" level="CRITICAL" enabled="false" />
+ <coding_rule class="VO_VOLATILE_REFERENCE_TO_ARRAY" level="MAJOR" enabled="true" />
+ <coding_rule class="VR_UNRESOLVABLE_REFERENCE" level="MAJOR" enabled="false" />
+ <coding_rule class="WA_AWAIT_NOT_IN_LOOP" level="CRITICAL" enabled="true" />
+ <coding_rule class="WA_NOT_IN_LOOP" level="CRITICAL" enabled="true" />
+ <coding_rule class="WL_USING_GETCLASS_RATHER_THAN_CLASS_LITERAL" level="CRITICAL" enabled="true" />
+ <coding_rule class="WMI_WRONG_MAP_ITERATOR" level="CRITICAL" enabled="true" />
+ <coding_rule class="WS_WRITEOBJECT_SYNC" level="CRITICAL" enabled="true" />
+ <coding_rule class="XFB_XML_FACTORY_BYPASS" level="CRITICAL" enabled="true" />
+ <coding_rule class="XSS_REQUEST_PARAMETER_TO_JSP_WRITER" level="CRITICAL" enabled="true" />
+ <coding_rule class="XSS_REQUEST_PARAMETER_TO_SEND_ERROR" level="CRITICAL" enabled="true" />
+ <coding_rule class="XSS_REQUEST_PARAMETER_TO_SERVLET_WRITER" level="CRITICAL" enabled="true" />
+ </profile>
+ </profiles>
+ <list size="0" />
+ </component>
+</project>
\ No newline at end of file
--- /dev/null
+/build
\ No newline at end of file
--- /dev/null
+{
+ "version": 2,
+ "artifactType": {
+ "type": "APK",
+ "kind": "Directory"
+ },
+ "applicationId": "com.ic.icfotaclient",
+ "variantName": "processFotaBrand8541eDebugResources",
+ "elements": [
+ {
+ "type": "SINGLE",
+ "filters": [],
+ "versionCode": 1,
+ "versionName": "1.0",
+ "outputFile": "FotaBrand8541e_V1_202101252023_debug.apk"
+ }
+ ]
+}
\ No newline at end of file
--- /dev/null
+{
+ "version": 2,
+ "artifactType": {
+ "type": "APK",
+ "kind": "Directory"
+ },
+ "applicationId": "com.ic.icfotaclient",
+ "variantName": "processFotaBrand8541eReleaseResources",
+ "elements": [
+ {
+ "type": "SINGLE",
+ "filters": [],
+ "versionCode": 1,
+ "versionName": "1.0",
+ "outputFile": "FotaBrand8541e_V1_202101260957_release.apk"
+ }
+ ]
+}
\ No newline at end of file
--- /dev/null
+plugins {
+ id 'com.android.application'
+ id 'kotlin-android'
+ id 'kotlin-android-extensions'
+}
+
+android {
+ compileSdkVersion 29
+
+
+ def currentVersionCode = getVersionCode()
+
+
+ defaultConfig {
+ applicationId "com.ic.icfotaclient"
+ minSdkVersion 19
+ targetSdkVersion 29
+ versionCode 1
+ versionName "1.0"
+
+ testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
+ }
+
+ signingConfigs {
+ key {
+ storeFile file("EQC_G5.jks")
+ storePassword "123456"
+ keyAlias "watch_g5"
+ keyPassword "123456"
+ }
+ }
+
+ productFlavors {
+
+ FotaBrand8521e {
+ flavorDimensions "FotaBrand21e"
+ buildConfigField "String", "brandName", "\"FotaBrand21e\""
+ }
+ FotaBrand8541e {
+ flavorDimensions "FotaBrand41e"
+ buildConfigField "String", "brandName", "\"FotaBrand41e\""
+ }
+ }
+
+ buildTypes {
+ release {
+ zipAlignEnabled true // zip 对齐优化
+ shrinkResources true // 移除未使用的资源文件
+ minifyEnabled true
+ proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
+ renderscriptDebuggable = false
+ debuggable = false
+ manifestPlaceholders = [:]
+ signingConfig signingConfigs.key
+ buildConfigField "String", "token", "\"fYldTgHDa7\""
+ }
+ debug {
+ signingConfig signingConfigs.key
+ buildConfigField "String", "token", "\"fYldTgHDa7\""
+ }
+ }
+ compileOptions {
+ sourceCompatibility JavaVersion.VERSION_1_8
+ targetCompatibility JavaVersion.VERSION_1_8
+ }
+ kotlinOptions {
+ jvmTarget = '1.8'
+ }
+
+ android.applicationVariants.all { variant ->
+ variant.outputs.all {
+ if (variant.buildType.name == "release") {
+ outputFileName = "${variant.getFlavorName()}_V${currentVersionCode}_${releaseTime()}_release.apk"
+ } else {
+ outputFileName = "${variant.getFlavorName()}_V${currentVersionCode}_${releaseTime()}_debug.apk"
+ }
+ }
+ }
+}
+
+static def releaseTime() {
+ return new Date().format("yyyyMMddHHmm", TimeZone.getTimeZone("GMT+08:00"))
+}
+
+def getVersionCode() {
+ def versionCodeFile = file('version_code.properties')
+ if (versionCodeFile.canRead()) {
+ Properties properties = new Properties()
+ properties.load(new FileInputStream(versionCodeFile))
+ def versionCode = properties['VERSION_CODE'].toInteger()//读取version_code.properties文件存放的版本号。
+ def runTasks = gradle.startParameter.taskNames
+ def task = ':app:assembleRelease'
+ if (task in runTasks) {
+ properties['VERSION_CODE'] = (++versionCode).toString()
+ properties.store(versionCodeFile.newWriter(), null)
+ }
+ return versionCode
+ } else {
+ throw new GradleException("无法读取version_code.properties文件!")
+ }
+}
+
+dependencies {
+ def lifecycle_version = "2.2.0"
+ implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:$lifecycle_version"
+ implementation files('libs/ota.da-sdk-v3.2.1.aar')
+ implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version"
+ implementation 'androidx.core:core-ktx:1.3.2'
+ implementation 'androidx.appcompat:appcompat:1.2.0'
+ implementation 'com.google.android.material:material:1.2.1'
+ implementation 'androidx.constraintlayout:constraintlayout:2.0.4'
+ testImplementation 'junit:junit:4.+'
+ androidTestImplementation 'androidx.test.ext:junit:1.1.2'
+ androidTestImplementation 'androidx.test.espresso:espresso-core:3.3.0'
+ implementation("com.squareup.okhttp3:okhttp:3.12.0")
+ implementation files('libs/android-logging-log4j-1.0.3.jar',
+ 'libs/ftp4j-1.7.2.jar',
+ 'libs/log4j-1.2.17.jar')
+ implementation 'com.google.code.gson:gson:2.8.6'
+}
\ No newline at end of file
--- /dev/null
+# Add project specific ProGuard rules here.
+# You can control the set of applied configuration files using the
+# proguardFiles setting in build.gradle.
+#
+# For more details, see
+# http://developer.android.com/guide/developing/tools/proguard.html
+
+# If your project uses WebView with JS, uncomment the following
+# and specify the fully qualified class name to the JavaScript interface
+# class:
+#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
+# public *;
+#}
+
+# Uncomment this to preserve the line number information for
+# debugging stack traces.
+#-keepattributes SourceFile,LineNumberTable
+
+# If you keep the line number information, uncomment this to
+# hide the original source file name.
+#-renamesourcefileattribute SourceFile
+
+# 保留四大组件,自定义的Application等等这些类不被混淆
+-keep public class * extends android.app.Activity
+-keep public class * extends android.app.Application
+-keep public class * extends android.app.Service
+-keep public class * extends android.content.BroadcastReceiver
+-keep public class * extends android.content.ContentProvider
+-keep public class * extends android.app.backup.BackupAgentHelper
+-keep public class * extends android.preference.Preference
+-keep public class * extends android.view.View
+
+-keep class android.support.** { *; }
+-keep public class * extends android.support.v4.**
+-keep public class * extends android.support.v7.**
+-keep public class * extends android.support.annotation.**
+-keep public class * extends android.app.Fragment
+-keep class com.google.** { *; }
+
+
+# 保留R下面的资源
+-keep class **.R$* {*;}
+-keepclassmembers class **.R$* {
+ public static <fields>;
+}
+
+# 保留在Activity中的方法参数是view的方法,
+# 这样以来我们在layout中写的onClick就不会被影响
+-keepclassmembers class * extends android.app.Activity{
+ public void *(android.view.View);
+}
+
+# 保留我们自定义控件(继承自View)不被混淆
+-keep public class * extends android.view.View{
+ *** get*();
+ void set*(***);
+ public <init>(android.content.Context);
+ public <init>(android.content.Context, android.util.AttributeSet);
+ public <init>(android.content.Context, android.util.AttributeSet, int);
+}
+
+################################## 项目类 ##################################
+
+-keep class com.facebook.** { *; }
+-keep class com.mediatek.** { *; }
+-keep class org.apache.** { *; }
+-keep class okio.Okio.** { *; }
+-keep class java.nio.** { *; }
+-keep class com.android.** { *; }
+-keep class android.** { *; }
+-keep class android.ddm.** { *; }
+-keep class com.alibaba.fastjson.** { *; }
+-keep class com.eqc.watch.launcher.WatchApplication { *; }
+#-keep class com.enqualcomm.support.EqcAlgorithmNative { *; }
+-dontwarn com.facebook.**
+-dontwarn com.mediatek.**
+-dontwarn java.nio.**
+-dontwarn com.alibaba.fastjson.**
+-dontwarn android.**
+-dontwarn com.android.**
+-dontwarn rx.internal.**
+
+-keep class com.ic.** { *; }
+-keep class com.enqualcomm.** { *; }
+-keep class com.sina.util.dnscache.** { *; }
+-keep class rx.internal.** { *; }
+-keep class java.util.concurrent.** { *; }
+-keep class se.wetcat.qatia.** { *; }
\ No newline at end of file
--- /dev/null
+package com.ic.icfotaclient
+
+import androidx.test.platform.app.InstrumentationRegistry
+import androidx.test.ext.junit.runners.AndroidJUnit4
+
+import org.junit.Test
+import org.junit.runner.RunWith
+
+import org.junit.Assert.*
+
+/**
+ * Instrumented test, which will execute on an Android device.
+ *
+ * See [testing documentation](http://d.android.com/tools/testing).
+ */
+@RunWith(AndroidJUnit4::class)
+class ExampleInstrumentedTest {
+ @Test
+ fun useAppContext() {
+ // Context of the app under test.
+ val appContext = InstrumentationRegistry.getInstrumentation().targetContext
+ assertEquals("com.ic.icfotaclient", appContext.packageName)
+ }
+}
\ No newline at end of file
--- /dev/null
+<?xml version="1.0" encoding="utf-8"?>
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.ic.icfotaclient"
+ android:sharedUserId="android.uid.system">
+
+ <uses-permission android:name="android.permission.REBOOT" />
+ <uses-permission android:name="android.permission.RECOVERY" />
+ <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
+ <uses-permission android:name="android.permission.ACCESS_CACHE_FILESYSTEM" />
+ <uses-permission android:name="android.permission.WRITE_MEDIA_STORAGE" />
+ <uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
+ <uses-permission android:name="android.permission.BROADCAST_STICKY" />
+ <uses-permission android:name="android.permission.ACCESS_MTP" />
+ <uses-permission android:name="android.permission.INTERNET" />
+ <uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
+ <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
+ <uses-permission android:name="android.permission.WAKE_LOCK" />
+ <uses-permission android:name="android.permission.READ_PHONE_STATE" />
+ <uses-permission android:name="android.Manifest.permission.RECOVERY" />
+
+
+ <application
+ android:name=".App"
+ android:allowBackup="true"
+ android:icon="@mipmap/ic_launcher"
+ android:label="@string/app_name"
+ android:supportsRtl="true"
+ android:theme="@style/Theme.ICFotaClient">
+ <activity android:name=".view.SystemUpdateActivity">
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN" />
+
+ <category android:name="android.intent.category.LAUNCHER" />
+ </intent-filter>
+ <intent-filter>
+ <action android:name="com.ic.ota.action.UPDATE_PAGE" />
+ <category android:name="android.intent.category.DEFAULT" />
+ </intent-filter>
+ </activity>
+ <activity android:name=".view.ApiTestActivity" />
+
+ <receiver
+ android:name=".presenter.OtaReceiver"
+ android:enabled="true"
+ android:exported="true">
+ <intent-filter>
+ <action android:name="android.intent.action.BOOT_COMPLETED" />
+ </intent-filter>
+ <intent-filter>
+ <action android:name="android.intent.action.MEDIA_MOUNTED" />
+ <data android:scheme="file" />
+ </intent-filter>
+ </receiver>
+ <service
+ android:name=".presenter.OtaService"
+ android:exported="true">
+ <intent-filter>
+ <action android:name="com.ic.ota.action.CHECK_OTA" />
+ </intent-filter>
+ </service>
+ </application>
+
+</manifest>
\ No newline at end of file
--- /dev/null
+// IOtaCheckCallback.aidl
+package com.ic.icfotaclient;
+
+import com.ic.icfotaclient.bean.OtaInfo;
+
+interface IOtaCheckCallback {
+ void onCheckResult(in OtaInfo otaInfo);
+
+ void onImeiCodeGet(in String imeiCode);
+
+ void onVersionCodeGet(in String versionCode);
+}
\ No newline at end of file
--- /dev/null
+// IOtaCheckService.aidl
+package com.ic.icfotaclient;
+
+import com.ic.icfotaclient.IOtaCheckCallback;
+
+interface IOtaCheckService {
+
+ oneway void checkHasNewVersion();
+
+ void setCallback(in IOtaCheckCallback callback);
+
+ oneway void getImeiCode();
+
+ oneway void getVersionCode();
+
+}
\ No newline at end of file
--- /dev/null
+// OtaInfo.aidl
+package com.ic.icfotaclient.bean;
+
+// Declare any non-default types here with import statements
+
+parcelable OtaInfo;
\ No newline at end of file
--- /dev/null
+package com.ic.icfotaclient
+
+import android.app.Application
+import android.content.Context
+import android.os.Handler
+import android.os.HandlerThread
+
+class App : Application() {
+
+ companion object {
+ lateinit var sContext: Context
+ lateinit var sHandler: Handler
+ }
+
+ override fun onCreate() {
+ super.onCreate()
+ sContext = applicationContext
+ val otaHandlerThread = HandlerThread("ic:ota-handler-thread")
+ otaHandlerThread.start()
+ sHandler = Handler(otaHandlerThread.looper)
+ }
+
+}
\ No newline at end of file
--- /dev/null
+package com.ic.icfotaclient.base
+
+import com.ic.icfotaclient.bean.DownloadInfo
+import com.ic.icfotaclient.bean.OtaInfo
+import com.ic.icfotaclient.repository.IOtaRepositoryCallback
+
+abstract class AbstractOtaRepository : IOtaBaseRepository {
+
+
+ var mNextRepository: IOtaBaseRepository? = null
+
+ override suspend fun setNextRepository(nextRepository: IOtaBaseRepository) {
+ this.mNextRepository = nextRepository
+ }
+
+ override suspend fun checkHasNewVersion(): OtaInfo? {
+ return mNextRepository?.checkHasNewVersion()
+ }
+
+ override suspend fun downloadPackage(callback: IOtaRepositoryCallback): DownloadInfo? {
+ return mNextRepository?.downloadPackage(callback)
+ }
+
+ override suspend fun reportInstallResult(isSuccess: Boolean) {
+ mNextRepository?.reportInstallResult(isSuccess)
+ }
+
+ override suspend fun reportResult(versionCode: String) {
+ mNextRepository?.reportResult(versionCode)
+ }
+
+ override suspend fun getPackagePath(): String? {
+ return mNextRepository?.getPackagePath()
+ }
+
+ override suspend fun deletePackage() {
+ mNextRepository?.deletePackage()
+ }
+
+ override suspend fun pauseDownload() {
+ mNextRepository?.pauseDownload()
+ }
+}
\ No newline at end of file
--- /dev/null
+package com.ic.icfotaclient.base
+
+import com.ic.icfotaclient.bean.DownloadInfo
+import com.ic.icfotaclient.bean.OtaInfo
+import com.ic.icfotaclient.repository.IOtaRepositoryCallback
+
+
+interface IOtaBaseRepository {
+ /**
+ * 检查是否有新的版本
+ */
+ suspend fun checkHasNewVersion(): OtaInfo?
+
+ /**
+ * 下载升级包
+ */
+ suspend fun downloadPackage(callback: IOtaRepositoryCallback): DownloadInfo?
+
+ /**
+ * 通知升级结果
+ */
+ suspend fun reportInstallResult(isSuccess: Boolean);
+
+ /**
+ * 通知更新成功,让服务器收费
+ */
+ suspend fun reportResult(versionCode: String)
+
+ /**
+ * 获取升级包的路径
+ */
+ suspend fun getPackagePath(): String?
+
+ /**
+ * 删除升级包
+ */
+ suspend fun deletePackage()
+
+ /**
+ * 设置下一级委托的Repository
+ */
+ suspend fun setNextRepository(nextRepository: IOtaBaseRepository)
+
+
+ /**
+ * 暂停下载
+ */
+ suspend fun pauseDownload()
+
+}
\ No newline at end of file
--- /dev/null
+package com.ic.icfotaclient.bean
+
+data class DownloadInfo(var path: String, val size: Long) {
+ override fun toString(): String {
+ return "DownloadInfo(path='$path', size=$size)"
+ }
+}
\ No newline at end of file
--- /dev/null
+package com.ic.icfotaclient.bean
+
+data class ICOtaInfo(
+ val AutoUpdate: Boolean,
+ val OtaPackageLength: Int,
+ val OtaPackageName: String,
+ val OtaPackageVersion: String,
+ val SystemVersion: String,
+ val UptoDate: Boolean,
+ val description: String,
+ val md5sum: String,
+ val url: String
+)
\ No newline at end of file
--- /dev/null
+package com.ic.icfotaclient.bean
+
+import android.os.Parcel
+import android.os.Parcelable
+
+/**
+ * 升级信息
+ */
+data class OtaInfo(
+ //是否强制升级
+ val isForce: Boolean,
+ //更新信息
+ val updateDesc: String?,
+ //版本号
+ val versionCode: String?,
+ //包的大小
+ val packageSize: Long,
+ //发布时间
+ val publishTime: Long,
+) : Parcelable {
+ constructor(parcel: Parcel) : this(
+ parcel.readByte() != 0.toByte(),
+ parcel.readString(),
+ parcel.readString(),
+ parcel.readLong(),
+ parcel.readLong(),
+ ) {
+ }
+
+ override fun toString(): String {
+ return "OtaInfo(isForce=$isForce," +
+ " updateDesc='$updateDesc'," +
+ " versionCode='$versionCode'," +
+ " packageSize=$packageSize," +
+ " publishTime=$publishTime)"
+ }
+
+ override fun writeToParcel(parcel: Parcel, flags: Int) {
+ parcel.writeByte(if (isForce) 1 else 0)
+ parcel.writeString(updateDesc)
+ parcel.writeString(versionCode)
+ parcel.writeLong(packageSize)
+ parcel.writeLong(publishTime)
+ }
+
+ override fun describeContents(): Int {
+ return 0
+ }
+
+ companion object CREATOR : Parcelable.Creator<OtaInfo> {
+ override fun createFromParcel(parcel: Parcel): OtaInfo {
+ return OtaInfo(parcel)
+ }
+
+ override fun newArray(size: Int): Array<OtaInfo?> {
+ return arrayOfNulls(size)
+ }
+ }
+}
--- /dev/null
+package com.ic.icfotaclient.presenter
+
+import android.content.BroadcastReceiver
+import android.content.Context
+import android.content.Intent
+import android.util.Log
+import com.ic.icfotaclient.utils.Constants
+
+/**
+ * 广播监听者:
+ * 监听开机广播,去检查是否有升级
+ * 其他广播//TODO:
+ */
+class OtaReceiver : BroadcastReceiver() {
+ companion object {
+ const val TAG = "OtaReceiver"
+ }
+
+ override fun onReceive(context: Context?, intent: Intent?) {
+ Log.d(TAG, "OtaReceiver...")
+ if (intent == null) {
+ return
+ }
+ val action = intent.action
+ Log.d(TAG, "action is == > $action")
+ when (action) {
+ Intent.ACTION_BOOT_COMPLETED -> {
+ //开机广播,拉起服务
+ val serviceIntent = Intent(context, OtaService::class.java)
+ serviceIntent.action = Constants.CMD_ACTION_BOOT_COMPLETED
+ context?.startService(serviceIntent)
+ }
+ }
+ }
+}
\ No newline at end of file
--- /dev/null
+package com.ic.icfotaclient.presenter
+
+import android.app.Service
+import android.content.Intent
+import android.os.IBinder
+import android.text.TextUtils
+import android.util.Log
+import androidx.lifecycle.*
+import com.ic.icfotaclient.App
+import com.ic.icfotaclient.IOtaCheckCallback
+import com.ic.icfotaclient.IOtaCheckService
+import com.ic.icfotaclient.utils.Constants
+import com.ic.icfotaclient.utils.OtaViewModelCreator
+import com.ic.icfotaclient.utils.VersionCheckFrom
+import com.ic.icfotaclient.utils.VersionInfoUtils
+
+/**
+ * 这个是专门给远程使用的,如果远程需要知道是否有更新的内容,可以通过AIDL通讯
+ * 绑定过来,检查是否有更新。
+ */
+class OtaService : Service(), ViewModelStoreOwner, LifecycleOwner {
+
+ companion object{
+ const val TAG = "OtaService"
+ }
+
+ override fun onBind(intent: Intent?): IBinder? {
+ return mServiceForRemote
+ }
+
+ override fun onCreate() {
+ super.onCreate()
+ Log.d(TAG,"on service created...")
+ OtaViewModelCreator.initOtaViewModelStoreOwner(this)
+ //观察数据变化
+ otaViewModel?.otaInfo?.observe(this, Observer {
+ Log.d(TAG,"on ota info change...$it")
+ //更新数据
+ mRemoteCallback?.onCheckResult(it)
+ })
+ }
+
+ private val otaViewModel by lazy {
+ OtaViewModelCreator.getOtaViewModel(null)
+ }
+
+ private var mRemoteCallback: IOtaCheckCallback? = null
+
+
+ private val mServiceForRemote = object : IOtaCheckService.Stub() {
+ //检查是否有新版本
+ override fun checkHasNewVersion() {
+ checkVersion(VersionCheckFrom.REMOTE_CHECK)
+ }
+
+ override fun setCallback(callback: IOtaCheckCallback?) {
+ //远程回调接口
+ mRemoteCallback = callback
+ }
+
+ override fun getImeiCode() {
+ val imei = VersionInfoUtils.getIMEI()
+ Log.d(TAG,"imei == > $imei")
+ mRemoteCallback?.onImeiCodeGet(imei)
+ }
+
+ override fun getVersionCode() {
+ val versionCode = VersionInfoUtils.getSystemVersion()
+ Log.d(TAG,"versionCode == > $versionCode")
+ mRemoteCallback?.onVersionCodeGet(versionCode)
+ }
+ }
+
+
+ override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
+ //判断指令是啥
+ val action = intent?.action
+ Log.d(TAG,"onStartCommand action -- > $action")
+ if (TextUtils.isEmpty(action)) {
+ return START_NOT_STICKY
+ }
+ //根据action做出处理
+ when (action) {
+ Constants.CMD_ACTION_BOOT_COMPLETED -> {
+ //开机完,创建一个ViewModel,生命周期与此服务生命周期挂钩
+ //以下动作,需要延时执行,因为IMEI号,网络这些还没准备好
+ //检查网络,如果网络okay了,就去检查版本更新
+ //如果网络不okay,那么就等待网络连接的广播,在连接的广播里去检查更新
+ App.sHandler.postDelayed({
+ checkVersion(VersionCheckFrom.BOOT_CHECK)
+ }, Constants.TIME_DELAY_BOOT)
+ //如果已经更新了,那么通知一下服务器
+ otaViewModel?.checkHasUpdated()
+ }
+ }
+
+ return super.onStartCommand(intent, flags, startId)
+ }
+
+ private fun checkVersion(from: VersionCheckFrom) {
+ val checkNetWork = otaViewModel?.checkNetWork()
+ Log.d(TAG,"checkNetWork == > $checkNetWork")
+ if (checkNetWork == true) {
+ //连接上了,那么就去检查更新
+ otaViewModel?.checkUpdate(from)
+ }
+ }
+
+ /**
+ * 释放资源
+ */
+ override fun onDestroy() {
+ super.onDestroy()
+ viewModel.clear()
+ }
+
+ private val viewModel by lazy {
+ ViewModelStore()
+ }
+
+ override fun getViewModelStore(): ViewModelStore {
+ return viewModel
+ }
+
+ override fun getLifecycle(): Lifecycle {
+ return LifecycleRegistry(this)
+ }
+}
\ No newline at end of file
--- /dev/null
+package com.ic.icfotaclient.presenter
+
+import android.content.Context
+import android.content.Intent
+import android.content.IntentFilter
+import android.os.BatteryManager
+import android.os.RecoverySystem
+import android.text.TextUtils
+import android.util.Log
+import android.view.Gravity
+import android.view.View
+import android.view.WindowManager
+import android.widget.Button
+import android.widget.TextView
+import android.widget.Toast
+import androidx.lifecycle.MutableLiveData
+import androidx.lifecycle.ViewModel
+import androidx.lifecycle.viewModelScope
+import com.ic.icfotaclient.App
+import com.ic.icfotaclient.R
+import com.ic.icfotaclient.bean.DownloadInfo
+import com.ic.icfotaclient.bean.OtaInfo
+import com.ic.icfotaclient.repository.CarOtaRepository
+import com.ic.icfotaclient.repository.ICOtaRepository
+import com.ic.icfotaclient.repository.IOtaRepositoryCallback
+import com.ic.icfotaclient.utils.*
+import com.ic.icfotaclient.view.SystemUpdateActivity
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.launch
+import kotlinx.coroutines.withContext
+import java.io.File
+
+
+/**
+ * 逻辑层,全局只有一个,生命周期与服务挂钩吧,检查完了就可以停掉了,不需要一直跑着。
+ * 如果有升级就去做事情,如果没有升级就自己退出,释放所有资源。
+ */
+class OtaViewModel : ViewModel() {
+
+ companion object {
+ const val TAG = "OtaViewModel"
+ }
+
+
+ private val mFirstRepository by lazy {
+ //Repository是责任链模式,所以要根据顺序排好
+ val carOtaRepository = CarOtaRepository()
+ val icOtaRepository = ICOtaRepository()
+ icOtaRepository.mNextRepository = carOtaRepository
+ icOtaRepository
+ }
+
+ //关心的数据
+ //升级数据
+ val otaInfo = MutableLiveData<OtaInfo?>()
+
+ //下载进度,百分比
+ val downloadProgress = MutableLiveData<Int>()
+
+ //是否下载完成
+ val currentActionState by lazy {
+ val mutableLiveData = MutableLiveData<UpdateActionState>()
+ mutableLiveData.postValue(UpdateActionState.NONE)
+ mutableLiveData
+ }
+
+ private var wifiConnected = false
+
+ //删除包的状态
+ fun checkNetWork(): Boolean {
+ var result: Boolean
+ wifiConnected = NetworkUtils.isWifiConnected(App.sContext)
+ Log.d(TAG, "WIFI CONNECTED == > $wifiConnected")
+ result = if (wifiConnected) {
+ isTipsWifiConnect.postValue(false)
+ wifiConnected
+ } else {
+ val isNetConnected = NetworkUtils.isNetConnected(App.sContext)
+ Log.d(TAG, "mobile net is connected == > $isNetConnected")
+ isNetConnected
+ }
+ if (!result) {
+ //无网络
+ currentActionState.postValue(UpdateActionState.NO_NETWORK)
+ }
+ return result
+ }
+
+ //检查更新,需要在子线程种完成
+ fun checkUpdate(from: VersionCheckFrom) {
+ if (currentActionState.value == UpdateActionState.CHECKING) {
+ Log.d(TAG, "checking....")
+ return
+ }
+ if (!checkNetWork()) {
+ currentActionState.value = UpdateActionState.NO_NETWORK
+ return
+ }
+ currentActionState.postValue(UpdateActionState.CHECKING)
+ viewModelScope.launch {
+ try {
+ //检查更新
+ val result: OtaInfo? = withContext(Dispatchers.IO) {
+ mFirstRepository.checkHasNewVersion()
+ }
+ //结果
+ Log.d(TAG, "check result ==> $result")
+ otaInfo.postValue(result)
+ //如果不为null,弹出一个系统级的dialog,也就是在任何地方都可以弹出的
+ currentActionState.postValue(
+ if (result == null) {
+ //已经是最新版本了
+ UpdateActionState.LAST_VERSION
+ } else {
+ //如果是强制升级的,不等通知用户结果了直接升级
+ if (result.isForce) {
+ Log.d(TAG, "force download data...")
+ //直接调用下载
+ downloadPackage()
+ } else {
+ //不是UI来的,可以创建一个弹窗,否则不弹窗,非强制升级
+ if (from != VersionCheckFrom.UI_CHECK) {
+ createNewVersionDialog(result.updateDesc ?: "无描述")
+ }
+ }
+ //有新版本
+ UpdateActionState.NEW_VERSION
+ }
+ )
+ } catch (e: Exception) {
+ Log.e(TAG, e.toString())
+ }
+ }
+ }
+
+ val downInfoResult = MutableLiveData<DownloadInfo>()
+
+
+ /**
+ * 暂停下载
+ */
+ fun pauseDownload() {
+ if (currentActionState.value != UpdateActionState.DOWNLOADING) {
+ Log.d(TAG, "current is not download...")
+ return
+ }
+ currentActionState.postValue(UpdateActionState.DOWNLOAD_PAUSE)
+ viewModelScope.launch {
+ withContext(Dispatchers.IO) {
+ Log.d(TAG, "download pause...")
+ mFirstRepository.pauseDownload()
+ }
+ }
+ }
+
+ val isTipsWifiConnect = MutableLiveData<Boolean>()
+
+
+ fun downloadPackage() {
+ if (currentActionState.value == UpdateActionState.DOWNLOADING) {
+ Log.d(TAG, "downloading...")
+ return
+ }
+ //判断size是否大于100M,如果大于100M的话那么就提示连接wifi进行下载
+ try {
+ if (otaInfo.value?.packageSize!! > 100000000L && !wifiConnected) {
+ isTipsWifiConnect.postValue(true)
+ //提示用户连接wifi下载
+ Log.d(TAG, "size == > ${otaInfo.value?.packageSize} please in wifi download...")
+ return
+ }
+ } catch (e: Exception) {
+ Log.d(TAG, "e", e)
+ }
+ currentActionState.postValue(UpdateActionState.DOWNLOADING)
+ viewModelScope.launch {
+ try {
+ val downResult = withContext(Dispatchers.IO) {
+ mFirstRepository.downloadPackage(mDownloadCallback)
+ }
+ if (downResult == null) {
+ currentActionState.postValue(UpdateActionState.ERROR)
+ } else {
+ downInfoResult.value = downResult
+ //下载成功以后,如果是强制升级的,直接走升级流程
+ if (otaInfo.value?.isForce == true) {
+ Log.d(TAG, "download completed and force to update....")
+ //直接调用升级
+ install()
+ } else {
+ currentActionState.postValue(UpdateActionState.DOWNLOADED)
+ }
+ }
+ } catch (e: java.lang.Exception) {
+ Log.e("e", e.toString())
+ }
+ }
+ }
+
+ /**
+ * 下载的回调
+ */
+ private val mDownloadCallback = object : IOtaRepositoryCallback {
+
+ override fun onDownloadProgressChange(currentProgress: Long, totalProgress: Long) {
+ //处理一下进度
+ val progress: Int = (currentProgress * 100.0f / totalProgress + 0.5f).toInt()
+ Log.d(TAG, "progress == > $progress")
+ downloadProgress.postValue(progress)
+ }
+
+ override fun onDownloadError(errorCode: Int, msg: String) {
+ Toast.makeText(App.sContext, "errorCode:$errorCode", Toast.LENGTH_LONG).show()
+ Log.d(TAG, "onDownloadError -- > $errorCode == > $msg")
+ if (errorCode == 0) {
+ //停止下载了,修改UI为继续下载
+ currentActionState.postValue(UpdateActionState.DOWNLOAD_PAUSE)
+ } else {
+ //其他错误,都提示用户重启
+ currentActionState.postValue(UpdateActionState.ERROR)
+ }
+ }
+
+ override fun onDownloadSuccess() {
+ Log.d(TAG, "onDownloadSuccess...")
+ //下载完成了
+ currentActionState.postValue(UpdateActionState.DOWNLOADED)
+ }
+
+ override fun onDeletePackageSuccess() {
+ //
+ }
+
+ override fun onDeletePackageFailed(errorCode: Int, msg: String) {
+ //
+ }
+ }
+
+ /**
+ * 创建一个弹窗,直接添加到窗口中.
+ */
+ private fun createNewVersionDialog(desc: String) {
+ val manager = App.sContext.getSystemService(Context.WINDOW_SERVICE) as WindowManager
+ val layoutParams = WindowManager.LayoutParams()
+ layoutParams.gravity = Gravity.CENTER
+ layoutParams.type = WindowManager.LayoutParams.TYPE_SYSTEM_ALERT
+ layoutParams.width = WindowManager.LayoutParams.WRAP_CONTENT
+ layoutParams.height = WindowManager.LayoutParams.WRAP_CONTENT
+ layoutParams.alpha = 1.0f
+ layoutParams.packageName = App.sContext.getPackageName()
+ layoutParams.buttonBrightness = 1f
+ layoutParams.windowAnimations = android.R.style.Animation_Dialog
+
+ val view: View =
+ View.inflate(App.sContext, R.layout.dialog_tips, null)
+ val descText: TextView = view.findViewById(R.id.description)
+ if (TextUtils.isEmpty(desc)) {
+ descText.text = "没有更新说明..."
+ } else {
+ descText.text = desc
+ }
+ val yesButton: Button = view.findViewById(R.id.button_ok) as Button
+ val noButton: Button = view.findViewById(R.id.button_cancel) as Button
+ yesButton.setOnClickListener {
+ //下载安装包
+ downloadPackage()
+ //跳转到下载界面
+ val targetIntent = Intent(view.context, SystemUpdateActivity::class.java)
+ targetIntent.flags = Intent.FLAG_ACTIVITY_NEW_TASK
+ view.context.startActivity(targetIntent)
+ manager.removeView(view)
+ }
+ noButton.setOnClickListener { manager.removeView(view) }
+ manager.addView(view, layoutParams)
+ }
+
+ /**
+ * 获取当前的版本号
+ */
+ fun getCurrentVersionCode(): String? {
+ return VersionInfoUtils.getSystemVersion()
+ }
+
+ fun getImeiCode(): String? {
+ return VersionInfoUtils.getIMEI()
+ }
+
+ /**
+ * 更新版本
+ */
+ fun install() {
+ viewModelScope.launch {
+ try {
+ //判断电量多少,如果低于50%不进行安装
+ val batteryStatus: Intent? =
+ IntentFilter(Intent.ACTION_BATTERY_CHANGED).let { filter ->
+ App.sContext.registerReceiver(null, filter)
+ }
+ val batteryPct: Float? = batteryStatus?.let { intent ->
+ val level: Int = intent.getIntExtra(BatteryManager.EXTRA_LEVEL, -1)
+ val scale: Int = intent.getIntExtra(BatteryManager.EXTRA_SCALE, -1)
+ level * 100f / scale.toFloat()
+ }
+ Log.d(TAG, "batteryPct == > $batteryPct")
+ if (batteryPct!! >= 50.0f) {
+ Log.d(TAG, "otaInfo.value!!.versionCode == > " + otaInfo.value!!.versionCode)
+ Log.d(TAG, "save target update version.")
+ //保存一个SP值,升级完了检查,用于通知服务器更新成功了.
+ val sharePreference = SPUtil.getSharePreference()
+ sharePreference.edit()
+ .putString(SPUtil.KEY_TARGET_VERSION_CODE, otaInfo.value!!.versionCode)
+ .apply()
+ //这里后面要改成我们自己的升级,而不是apk的升级方式
+ withContext(Dispatchers.IO) {
+ //进行电量检查
+ val path = downInfoResult.value!!.path;
+ //val path = "/data/media/0/sl8541e_1h10_oversea-ota-04210.zip"
+ val tempFile = File(path)
+ Log.d(TAG, "tempFile.exists() == > ${tempFile.exists()}")
+ Log.d(TAG, "install path is == > $path")
+ //先验证,再安装
+ try {
+ RecoverySystem.verifyPackage(File(path), null, null)
+ Log.d(TAG, "verifyPackageFinished...")
+ RecoverySystem.installPackage(
+ App.sContext,
+ File(path)
+ )
+ } catch (e: Exception) {
+ e.printStackTrace()
+ }
+ }
+ } else {
+ Log.d(TAG, "battery level is not enough...")
+ //电量不够,不进行升级
+ currentActionState.postValue(UpdateActionState.BATTERY_LEVEL_NOT_ENOUGH)
+ }
+ } catch (e: Exception) {
+ Log.e("e", e.toString())
+ }
+ }
+ }
+
+ /**
+ * 检查有没有更新成了,通过检查SP即可
+ */
+ fun checkHasUpdated() {
+ val sharePreference = SPUtil.getSharePreference()
+ val versionRecord = sharePreference.getString(SPUtil.KEY_TARGET_VERSION_CODE, "")
+ //获取当前的系统版本号
+ val systemVersion = VersionInfoUtils.getSystemVersion()
+ Log.d(TAG, "system version code is == > $systemVersion")
+ Log.d(TAG, "versionRecord code is == > $versionRecord")
+ if (TextUtils.isEmpty(versionRecord)) {
+ if (versionRecord == systemVersion) {
+ try {
+ viewModelScope.launch {
+ withContext(Dispatchers.IO) {
+ Log.d(TAG, "post install result..success.")
+ mFirstRepository.reportInstallResult(true)
+ mFirstRepository.reportResult(versionRecord!!)
+ //删除文件
+ mFirstRepository.deletePackage()
+ }
+ }
+ } catch (e: Exception) {
+ e.printStackTrace()
+ }
+ } else {
+ try {
+ viewModelScope.launch {
+ withContext(Dispatchers.IO) {
+ Log.d(TAG, "post install result..failed.")
+ mFirstRepository.reportInstallResult(false)
+ mFirstRepository.reportResult(versionRecord!!)
+ }
+ }
+ } catch (e: Exception) {
+ e.printStackTrace()
+ }
+ }
+ sharePreference.edit().putString(SPUtil.KEY_TARGET_VERSION_CODE, "").apply()
+ }
+ }
+
+}
\ No newline at end of file
--- /dev/null
+package com.ic.icfotaclient.repository
+
+import android.os.Bundle
+import android.util.Log
+import com.ic.icfotaclient.BuildConfig
+import com.ic.icfotaclient.base.IOtaBaseRepository
+import com.ic.icfotaclient.bean.DownloadInfo
+import com.ic.icfotaclient.bean.OtaInfo
+import com.ic.icfotaclient.presenter.OtaViewModel
+import com.ic.icfotaclient.utils.CarOtaApiUtils
+import com.ic.icfotaclient.utils.VersionInfoUtils
+import com.ota.fw.IDownloadEvent
+import com.ota.fw.SysInfo
+import java.io.BufferedInputStream
+import java.io.BufferedOutputStream
+import java.io.FileInputStream
+import java.io.FileOutputStream
+
+/**
+ * 科洛达的Repository
+ */
+class CarOtaRepository : IOtaBaseRepository {
+
+ companion object {
+ const val TAG = "CarOtaRepository"
+ }
+
+ private var mCallback: IOtaRepositoryCallback? = null
+
+ override suspend fun checkHasNewVersion(): OtaInfo? {
+ val systemInfo = SysInfo(
+ BuildConfig.token,
+ VersionInfoUtils.getIMEI(),
+ VersionInfoUtils.getSystemVersion(),
+ BuildConfig.brandName,
+ VersionInfoUtils.getOtaProductName()
+ )
+ val checkResult = CarOtaApiUtils.mClient.check(systemInfo) ?: return null
+ Log.d(TAG, "checkResult == > $checkResult")
+ //转成我们的bean类
+ return OtaInfo(
+ checkResult.mode != 0,//0表示用户执行,1/2表示强制下载/强制更新
+ checkResult.releaseNote,
+ checkResult.targetVersion,
+ checkResult.size,
+ System.currentTimeMillis()//科洛达的没有时间这项
+ )
+ }
+
+ override suspend fun downloadPackage(callback: IOtaRepositoryCallback): DownloadInfo? {
+ mCallback = callback
+ val download = CarOtaApiUtils.mClient.download(object : IDownloadEvent {
+ override fun onDownloading(downloaded: Long, total: Long, extra: Bundle?) {
+ Log.d(TAG, "onDownloading...$downloaded -- > $total")
+ mCallback?.onDownloadProgressChange(downloaded, total)
+ }
+
+ override fun onDownloaded(extra: Bundle?) {
+ Log.d(TAG, "onDownloaded...$extra")
+ mCallback?.onDownloadSuccess()
+ }
+
+ override fun onError(errCode: Int, extra: Bundle?) {
+ Log.d(TAG, "onError...$errCode")
+ mCallback?.onDownloadError(errCode, "下载出错")
+ }
+
+ override fun onRetry(code: Int) {
+ Log.d(TAG, "onRetry...$code")
+ }
+
+ }) ?: return null
+ //转一下
+ Log.d(TAG, "download == > ${download.file}")
+ val targetFile = VersionInfoUtils.getFlashPath()
+ Log.d(OtaViewModel.TAG, "copy finished to target path == > $targetFile")
+ val fis = FileInputStream(targetFile);
+ val fos = FileOutputStream(targetFile)
+ //复制到对应的目录下进行
+ val bis = BufferedInputStream(fis)
+ val bos = BufferedOutputStream(fos)
+ val buf = ByteArray(1024)
+ var len = 0;
+ while (true) {
+ len = bis.read(buf)
+ if (len == -1) break;
+ bos.write(buf, 0, len)
+ }
+ fis.close()
+ fos.close()
+ val downloadInfo = DownloadInfo(download.file.path, 0L)
+ downloadInfo.path = targetFile
+ return downloadInfo
+ }
+
+ override suspend fun reportInstallResult(isSuccess: Boolean) {
+ CarOtaApiUtils.mClient.reportUpdateResult(isSuccess, 0)
+ Log.d(TAG, "report result == isSuccess == > $isSuccess")
+ }
+
+ override suspend fun reportResult(versionCode: String) {
+ CarOtaApiUtils.mClient.reportUpdateResultByVersion(versionCode)
+ Log.d(TAG, "report == versionCode == > $versionCode")
+ }
+
+ override suspend fun getPackagePath(): String? {
+ return null
+ }
+
+ override suspend fun deletePackage() {
+ //传说他们自己会删除的,通报了成功以后
+ }
+
+ override suspend fun pauseDownload() {
+ Log.d(TAG, "pauseDownload...")
+ CarOtaApiUtils.mClient.stopDownload()
+ }
+}
\ No newline at end of file
--- /dev/null
+package com.ic.icfotaclient.repository
+
+import android.util.Log
+import android.widget.Toast
+import com.ic.icfotaclient.App
+import com.ic.icfotaclient.R
+import com.ic.icfotaclient.base.AbstractOtaRepository
+import com.ic.icfotaclient.bean.DownloadInfo
+import com.ic.icfotaclient.bean.OtaInfo
+import com.ic.icfotaclient.utils.ICOtaApiUtils
+import com.ic.icfotaclient.utils.VersionInfoUtils
+import java.io.File
+
+/**
+ * 有不服气,可以自己复写,默认是交给下一级Repository处理。
+ * 比如说英卡的OTA,目前需要去统计一下数据,去调用一下检查更新即可。不需要做其他的工作。
+ */
+class ICOtaRepository : AbstractOtaRepository() {
+
+
+ private var checkIcVersion: OtaInfo? = null
+
+ /**
+ * 检查是否有更新,英卡的服务器
+ */
+ override suspend fun checkHasNewVersion(): OtaInfo? {
+ try {
+ //调用一下英卡的接口
+ checkIcVersion = ICOtaApiUtils.checkIcVersion()
+ if (checkIcVersion != null) {
+ return checkIcVersion
+ }
+ } catch (e: Exception) {
+ Log.e("e==>", e.toString())
+ }
+ //如果没有则走下一级服务器
+ return super.checkHasNewVersion()
+ }
+
+
+ override suspend fun pauseDownload() {
+ if (checkIcVersion != null) {
+ Toast.makeText(
+ App.sContext,
+ App.sContext.getString(R.string.unsupport_pause_download),
+ Toast.LENGTH_LONG
+ ).show()
+ }
+ super.pauseDownload()
+ }
+
+
+ override suspend fun downloadPackage(callback: IOtaRepositoryCallback): DownloadInfo? {
+ //下载安装包,直接当前线程调用即可
+ //如果这个有安装包就不需要往下去下载了。
+ if (checkIcVersion != null) {
+ //说明是走英卡的服务器更新的,从英卡服务器下载更新包
+ return ICOtaApiUtils.downloadPackage(callback)
+ }
+ return super.downloadPackage(callback)
+ }
+
+ override suspend fun reportResult(versionCode: String) {
+ if (checkIcVersion != null) {
+ return
+ }
+ super.reportResult(versionCode)
+ }
+
+
+ override suspend fun reportInstallResult(isSuccess: Boolean) {
+ if (checkIcVersion != null) {
+ return
+ }
+ super.reportInstallResult(isSuccess)
+ }
+
+ override suspend fun deletePackage() {
+ //删除flash_root下的
+ val file = File(VersionInfoUtils.getFlashPath())
+ if (file.exists()) {
+ file.delete()
+ }
+ super.deletePackage()
+ }
+
+}
\ No newline at end of file
--- /dev/null
+package com.ic.icfotaclient.repository
+
+interface IOtaRepositoryCallback {
+
+ /**
+ * 下载进度
+ */
+ fun onDownloadProgressChange(currentProgress: Long, totalProgress: Long)
+
+ /**
+ * 0-停止下载 -1-连接错误 -2-下载中断 -3-下载文件校验失败 -4-其他错误 -5-文件校验失败
+ */
+ fun onDownloadError(errorCode: Int, msg: String)
+
+ /**
+ * 下载成功
+ */
+ fun onDownloadSuccess()
+
+ /**
+ * 删除升级包成功
+ */
+ fun onDeletePackageSuccess()
+
+ /**
+ * 删除升级包失败
+ */
+ fun onDeletePackageFailed(errorCode: Int, msg: String)
+}
\ No newline at end of file
--- /dev/null
+package com.ic.icfotaclient.utils
+
+import android.util.Log
+import com.ic.icfotaclient.App
+import com.ota.fw.FotaClient
+import com.ota.fw.IHookInstall
+
+object CarOtaApiUtils {
+
+ val mClient by lazy {
+ FotaClient.buildBasic(App.sContext, Constants.OTA_URL, null, IHookInstall { context, file ->
+ Log.d(
+ "CarOtaApiUtils", "install success == > " + file.path
+ )
+ 1//这个值是自定义值,在install的时候会返回
+ })
+ }
+
+}
\ No newline at end of file
--- /dev/null
+package com.ic.icfotaclient.utils
+
+object Constants {
+ const val CMD_ACTION_BOOT_COMPLETED = "cmd_action_boot_completed"
+ const val CMD_ACTION_ACTIVITY_START = "cmd_action_activity_start"
+ const val OTA_URL = "https://api.ota.limios.net/conn/api/v1/"
+
+ //收到开机广播后,10秒钟以后,再去检查系统更新
+ const val TIME_DELAY_BOOT = 10 * 1000L
+
+
+}
\ No newline at end of file
--- /dev/null
+package com.ic.icfotaclient.utils
+
+import android.util.Log
+import com.google.gson.Gson
+import com.ic.icfotaclient.App
+import com.ic.icfotaclient.R
+import com.ic.icfotaclient.bean.DownloadInfo
+import com.ic.icfotaclient.bean.ICOtaInfo
+import com.ic.icfotaclient.bean.OtaInfo
+import com.ic.icfotaclient.repository.IOtaRepositoryCallback
+import okhttp3.OkHttpClient
+import okhttp3.Request
+import java.io.BufferedInputStream
+import java.io.BufferedOutputStream
+import java.io.FileOutputStream
+import java.net.HttpURLConnection
+import java.net.URI
+import java.security.SecureRandom
+import java.security.cert.X509Certificate
+import java.util.concurrent.TimeUnit
+import javax.net.ssl.SSLContext
+import javax.net.ssl.TrustManager
+import javax.net.ssl.X509TrustManager
+
+object ICOtaApiUtils {
+
+ val TAG = "ICOtaApiUtils"
+
+ fun checkIcVersion(): OtaInfo? {
+ val uri = URI(getRemoteUri())
+ val url = uri.toString()
+ return getUpdateInfo(url)
+ }
+
+ private fun initOkHttp() {
+ //首先创建OkHttpClient.build进行信任所有证书配置
+ //首先创建OkHttpClient.build进行信任所有证书配置
+ val okhttpClient = OkHttpClient().newBuilder()
+ //信任所有服务器地址
+ //信任所有服务器地址
+ okhttpClient.hostnameVerifier { s, sslSession -> //设置为true
+ true
+ }
+ //创建管理器
+ //创建管理器
+ val trustAllCerts =
+ arrayOf<TrustManager>(object : X509TrustManager {
+ override fun checkClientTrusted(
+ x509Certificates: Array<X509Certificate>,
+ s: String
+ ) {
+ }
+
+ override fun checkServerTrusted(
+ x509Certificates: Array<X509Certificate>,
+ s: String
+ ) {
+ }
+
+ override fun getAcceptedIssuers(): Array<X509Certificate> {
+ return arrayOf()
+ }
+ })
+ try {
+ val sslContext = SSLContext.getInstance("TLS")
+ sslContext.init(null, trustAllCerts, SecureRandom())
+
+ //为OkHttpClient设置sslSocketFactory
+ okhttpClient.sslSocketFactory(sslContext.socketFactory)
+ } catch (e: Exception) {
+ e.printStackTrace()
+ }
+ okHttpClient = okhttpClient
+ .connectTimeout(100000, TimeUnit.SECONDS)
+ .writeTimeout(100000, TimeUnit.SECONDS)
+ .readTimeout(100000, TimeUnit.SECONDS)
+ .build()
+ }
+
+ private var okHttpClient: OkHttpClient? = null
+
+ private var otaInfo: ICOtaInfo? = null
+
+ private fun getUpdateInfo(
+ url: String?
+ ): OtaInfo? {
+ if (okHttpClient == null) {
+ initOkHttp()
+ }
+ val request: Request = Request.Builder().url(url).build()
+
+ val execute = okHttpClient?.newCall(request)?.execute()
+ //解析数据
+ if (execute != null && execute.code() == HttpURLConnection.HTTP_OK) {
+ val body = execute.body() ?: return null
+ val json = body.string()
+ Log.d(TAG, "result == > $json")
+ val gson = Gson()
+ otaInfo = gson.fromJson<ICOtaInfo>(json, ICOtaInfo::class.java)
+ if (otaInfo != null) {
+ return OtaInfo(
+ otaInfo!!.AutoUpdate,
+ otaInfo!!.description,
+ otaInfo!!.OtaPackageVersion,
+ otaInfo!!.OtaPackageLength.toLong(),
+ System.currentTimeMillis()
+ )
+ }
+ }
+ return null
+ }
+
+ private fun getRemoteUri(): String? {
+ var server: String = getRemoteHost()
+ val productname: String? = VersionInfoUtils.getOtaProductName()
+ val pkgversion: String? = VersionInfoUtils.getSystemVersion()
+ val imei: String? = VersionInfoUtils.getIMEI()
+ if (server.startsWith("http://")) {
+ server = server.replace("http://", "")
+ }
+ //为了兼容之前的只设置ip和端口的设置方式
+ var actionUrl =
+ "http://$server/ota.jsp?product=$productname&pkgver=$pkgversion&imei=$imei"
+ if (server.contains("/")) { //此时说明不是ip地址,而是个url
+ actionUrl =
+ "http://$server?product=$productname&pkgver=$pkgversion&imei=$imei"
+ }
+ Log.d(TAG, "actionUrl == > $actionUrl")
+ return actionUrl
+ }
+
+
+ private fun getRemoteHost(): String {
+ var remoteHost: String? =
+ SysPropUtils.get("persist.sys.ota.host", null)
+ if (remoteHost == null || remoteHost.isEmpty() || remoteHost == "unknown") {
+ remoteHost = "icfota.snicker.com.cn:8089"
+ }
+ Log.d(TAG, "remoteHost == > $remoteHost")
+ return remoteHost
+ }
+
+ /**
+ * 下载更新包
+ */
+ fun downloadPackage(
+ callback: IOtaRepositoryCallback?
+ ): DownloadInfo? {
+ if (okHttpClient == null) {
+ initOkHttp()
+ }
+ if (otaInfo == null) {
+ return null
+ }
+ val request: Request = Request.Builder().url(
+ otaInfo!!.url
+ ).build()
+ try {
+ val total = otaInfo!!.OtaPackageLength
+ val response = okHttpClient?.newCall(request)?.execute()
+ if (response?.code() == HttpURLConnection.HTTP_OK) {
+ val inputStream = response.body()?.byteStream()
+ val outPutStream = FileOutputStream(VersionInfoUtils.getFlashPath())
+ val bis = BufferedInputStream(inputStream)
+ val bos = BufferedOutputStream(outPutStream)
+ val buf = ByteArray(1024)
+ var len = 0;
+ var current = 0;
+ while (true) {
+ len = bis.read(buf)
+ if (len == -1) break;
+ current += len;
+ bos.write(buf, 0, len)
+ callback?.onDownloadProgressChange(current.toLong(), total.toLong())
+ }
+ inputStream?.close()
+ outPutStream.close()
+ }
+ callback?.onDownloadSuccess()
+ return DownloadInfo(VersionInfoUtils.getFlashPath(), total.toLong())
+ } catch (e: Exception) {
+ e.printStackTrace()
+ callback?.onDownloadError(0, App.sContext.getString(R.string.error_tips_text))
+ }
+ return null
+ }
+}
--- /dev/null
+package com.ic.icfotaclient.utils
+
+import android.Manifest.permission.ACCESS_NETWORK_STATE
+import android.content.Context
+import android.net.ConnectivityManager
+import android.net.NetworkCapabilities
+import android.os.Build
+import androidx.annotation.RequiresPermission
+
+
+/**
+ * 网络状态工具类,用于获取网络状态
+ */
+object NetworkUtils {
+
+ /**
+ * 网络是否已连接
+ *
+ * @return true:已连接 false:未连接
+ */
+ fun isNetConnected(context: Context): Boolean {
+ val manager = context.getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
+ val networkCapabilities = manager.getNetworkCapabilities(manager.activeNetwork)
+ if (networkCapabilities != null) {
+ return (networkCapabilities.hasTransport(NetworkCapabilities.TRANSPORT_WIFI)
+ || networkCapabilities.hasTransport(NetworkCapabilities.TRANSPORT_CELLULAR)
+ || networkCapabilities.hasTransport(NetworkCapabilities.TRANSPORT_ETHERNET))
+ }
+ } else {
+ val networkInfo = manager.activeNetworkInfo
+ return networkInfo != null && networkInfo.isConnected
+ }
+ return false
+ }
+
+ /**
+ * Wifi是否已连接
+ *
+ * @return true:已连接 false:未连接
+ */
+ fun isWifiConnected(context: Context): Boolean {
+ val manager = context.getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager
+ if (manager != null) {
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
+ val networkCapabilities = manager.getNetworkCapabilities(manager.activeNetwork)
+ if (networkCapabilities != null) {
+ return networkCapabilities.hasTransport(NetworkCapabilities.TRANSPORT_WIFI)
+ }
+ } else {
+ val networkInfo = manager.activeNetworkInfo
+ return networkInfo != null && networkInfo.isConnected && networkInfo.type == ConnectivityManager.TYPE_WIFI
+ }
+ }
+ return false
+ }
+
+ /**
+ * 是否为流量
+ */
+ fun isMobileData(context: Context): Boolean {
+ val manager = context.getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager
+ if (manager != null) {
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
+ val networkCapabilities = manager.getNetworkCapabilities(manager.activeNetwork)
+ if (networkCapabilities != null) {
+ return networkCapabilities.hasTransport(NetworkCapabilities.TRANSPORT_CELLULAR)
+ }
+ } else {
+ val networkInfo = manager.activeNetworkInfo
+ return networkInfo != null && networkInfo.isConnected && networkInfo.type == ConnectivityManager.TYPE_MOBILE
+ }
+ }
+ return false
+ }
+}
\ No newline at end of file
--- /dev/null
+package com.ic.icfotaclient.utils
+
+import android.util.Log
+import androidx.lifecycle.ViewModelProvider
+import androidx.lifecycle.ViewModelStoreOwner
+import com.ic.icfotaclient.presenter.OtaViewModel
+
+/**
+ * 获取OtaViewModel,生命周期与OtaService挂钩
+ * 可以理解为一个单例
+ */
+object OtaViewModelCreator {
+
+ const val TAG = "OtaViewModelCreator"
+
+ lateinit var mVmStore: ViewModelStoreOwner
+
+ private var viewModel: OtaViewModel? = null
+
+
+ /**
+ * 首先要把owner设置进来
+ */
+ fun initOtaViewModelStoreOwner(vmStore: ViewModelStoreOwner) {
+ mVmStore = vmStore
+ viewModel = ViewModelProvider(mVmStore).get(OtaViewModel::class.java)
+ for (onViewModelInitListener in callbackList) {
+ onViewModelInitListener.onViewModelInit(viewModel)
+ }
+ callbackList.clear()
+ }
+
+ private val callbackList = arrayListOf<OnViewModelInitListener>()
+
+ /**
+ * 获取OtaViewModel,有可能为null,如果OtaService退出的话,会清掉的
+ * callback可以为null,如果为null,则不通知结果,但是会返回。
+ * 建议界面调用的时候,通过接口获取viewModel
+ */
+ fun getOtaViewModel(callback: OnViewModelInitListener?): OtaViewModel? {
+ //可以传null
+ if (!this::mVmStore.isInitialized) {
+ Log.d(
+ TAG,
+ "you must call initOtaViewModelStoreOwner() first to setup ViewModelStoreOwner"
+ )
+ //没有初始化,如果callback不为null,添加到集合里
+ if (callback != null) {
+ callbackList.add(callback)
+ }
+ } else {
+ //如果有接口设置过来,那就接口也返回一下
+ callback?.onViewModelInit(viewModel)
+ }
+ return viewModel
+ }
+
+ interface OnViewModelInitListener {
+ fun onViewModelInit(otaViewModel: OtaViewModel?);
+ }
+
+}
\ No newline at end of file
--- /dev/null
+package com.ic.icfotaclient.utils
+
+import android.content.Context
+import android.content.SharedPreferences
+import com.ic.icfotaclient.App
+
+object SPUtil {
+
+
+ const val SP_NAME = "update_record"
+ const val KEY_TARGET_VERSION_CODE = "key_target_version_code"
+
+ fun getSharePreference(): SharedPreferences {
+ return App.sContext.getSharedPreferences(SP_NAME, Context.MODE_PRIVATE);
+ }
+
+}
\ No newline at end of file
--- /dev/null
+package com.ic.icfotaclient.utils
+
+import java.lang.reflect.InvocationTargetException
+import java.lang.reflect.Method
+
+/**
+ * 系统属性获取的工具
+ */
+object SysPropUtils {
+
+ private var sysPropGet: Method? = null
+ private var sysPropSet: Method? = null
+
+ init {
+ try {
+ val forName = Class.forName("android.os.SystemProperties")
+ val methods = forName.methods
+ for (method in methods) {
+ if ("get" == method.name) {
+ sysPropGet = method
+ } else {
+ sysPropSet = method
+ }
+ }
+ } catch (e: ClassNotFoundException) {
+ e.printStackTrace()
+ }
+ }
+
+ operator fun get(name: String?, default_value: String?): String? {
+ try {
+ return sysPropGet!!.invoke(null, name, default_value) as String
+ } catch (e: IllegalArgumentException) {
+ e.printStackTrace()
+ } catch (e: IllegalAccessException) {
+ e.printStackTrace()
+ } catch (e: InvocationTargetException) {
+ e.printStackTrace()
+ }
+ return default_value
+ }
+
+ operator fun set(name: String?, value: String?) {
+ try {
+ sysPropSet!!.invoke(null, name, value)
+ } catch (e: IllegalArgumentException) {
+ e.printStackTrace()
+ } catch (e: IllegalAccessException) {
+ e.printStackTrace()
+ } catch (e: InvocationTargetException) {
+ e.printStackTrace()
+ }
+ }
+}
\ No newline at end of file
--- /dev/null
+package com.ic.icfotaclient.utils
+
+enum class UpdateActionState {
+ NONE,/*初始状态,显示:检查更新,isEnable = true*/
+ CHECKING,/*正在检查更新,显示:正在检查更新 isEnable = false*/
+ NEW_VERSION,/*显示:下载新版本 isEnable = true*/
+ LAST_VERSION,/*显示:已是最新版本,isEnable = false*/
+ DOWNLOADING,/*显示暂停下载 isEnable = true*/
+ DOWNLOADED,/*显示:安装,isEnable = true*/
+ DOWNLOAD_PAUSE,/*显示继续下载,isEnable=true*/
+ NO_NETWORK,/*显示无网络,isEnable=false*/
+ ERROR,/*恢复默认,显示检查更新 isEnable=false*/
+ BATTERY_LEVEL_NOT_ENOUGH,/*电池电量不足50%,isEnable=false*/
+}
\ No newline at end of file
--- /dev/null
+package com.ic.icfotaclient.utils
+
+enum class VersionCheckFrom {
+ /*远程调用检查*/
+ REMOTE_CHECK,
+
+ /*UI检查更新时的check*/
+ UI_CHECK,
+
+ /*开机检查的check*/
+ BOOT_CHECK
+}
\ No newline at end of file
--- /dev/null
+package com.ic.icfotaclient.utils
+
+import android.annotation.SuppressLint
+import android.content.Context
+import android.os.Build
+import android.telephony.TelephonyManager
+import android.text.TextUtils
+import android.util.Log
+import com.ic.icfotaclient.App
+import java.io.File
+
+/**
+ * 工具类
+ * 获取IMEI号
+ * 获取版本号
+ * 获取版本名称
+ */
+object VersionInfoUtils {
+
+ const val TAG = "VersionInfoUtils"
+
+ private const val KeyIMEI = "persist.sys.protocol_IMEI"
+
+ /**
+ * 获取IMEI号
+ */
+ //获取手机IMEI号
+ @SuppressLint("MissingPermission", "HardwareIds")
+ fun getIMEI(): String? {
+ //优先从Pro中获取,有可能通过短信修改了IMEI号
+ var imei: String? = SysPropUtils[KeyIMEI, ""]
+ if (TextUtils.isEmpty(imei)) {
+ imei = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
+ (App.sContext.getSystemService(Context.TELEPHONY_SERVICE) as TelephonyManager).imei
+ } else {
+ (App.sContext.getSystemService(Context.TELEPHONY_SERVICE) as TelephonyManager).deviceId
+ }
+ }
+ Log.d("imei", "imei = > $imei")
+ return imei
+ }
+
+
+ fun getOtaProductName(): String? {
+ var productName: String =
+ //C41S-D-EU-P2
+ SysPropUtils.get("ro.product.model", "") as String
+ if (productName.contains(" ")) {
+ productName = productName.replace(" ".toRegex(), "")
+ }
+ Log.d(TAG, "product name -= > $productName")
+ return productName
+ }
+
+ fun getSystemVersion(): String? {
+ var version: String =
+ //C41S-D-EU-P2-V0.3.37.20210112.184146
+ SysPropUtils.get("ro.build.display.id", "") as String
+ if (version.isEmpty()) {
+ version = "1.0.0"
+ }
+ Log.d(TAG, "system version is == > $version")
+ return version
+ }
+
+
+ private fun getMultiUserState(): Boolean {
+ var multiUser: String? =
+ SysPropUtils.get("ro.factory.hasUMS", "")
+ if (multiUser != null && multiUser.isNotEmpty()) {
+ return multiUser != "true"
+ }
+ multiUser = SysPropUtils.get("ro.factory.storage_policy", "")
+ if (multiUser != null && multiUser.isNotEmpty()) {
+ return multiUser == "1"
+ }
+ val property = SysPropUtils.get("ro.boot.hardware", "")
+ return property != null && property.contains("sl8541e")
+ }
+
+
+ var flash_root =
+ "/data" //Environment.getExternalStorageDirectory().getAbsolutePath();
+
+ var DATA_ROOT = "/data/media/0"
+
+ private const val updateFileName = "update.zip"
+
+
+ fun getFlashPath(): String {
+ //移动到对应的位置上
+ if (getMultiUserState()) {
+ flash_root = DATA_ROOT
+ }
+
+ return flash_root + File.separator + updateFileName
+ }
+}
\ No newline at end of file
--- /dev/null
+package com.ic.icfotaclient.view
+
+import android.os.Bundle
+import androidx.appcompat.app.AppCompatActivity
+import com.ic.icfotaclient.BuildConfig
+import com.ic.icfotaclient.R
+
+import com.ic.icfotaclient.utils.VersionInfoUtils
+import com.ota.fw.FotaClient
+import com.ota.fw.IDownloadEvent
+import com.ota.fw.IUpdateInfo
+import com.ota.fw.SysInfo
+
+class ApiTestActivity : AppCompatActivity() {
+
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+ setContentView(R.layout.activity_api_test)
+ //测试api
+ val client =
+ FotaClient.buildBasic(this, "http://api.ota.limios.net/conn/api/v1/", {
+ //网络是否可用?
+ true
+ }, { context, file ->
+ //context-上下文
+ //path-安装包路径
+ //调用 FotaClient. install 后,经过包二次校验,状态回报等操作后,就会调用到此接口来进行升级操
+ //作。返回值可以自定义,最终通过 FotaClient. install 返回
+ println("context == > $context")
+ println("file == > $file")
+ 0//这个值会返回到install里
+ })
+
+ //SysInfo(String projectToken, String serialNum, String srcVer, String brand, String model, Bundle extra)
+ //serialNum-设备唯一识别码(IMEI 或 sn 号或其他参数)
+ //srcVer-设备当前版本号
+ //brand-设备 brand
+ //model-设备 model
+ //extra-设备额外信息,不设置可传 null
+ //projectToken-项目 token,后端生成,由科络达分配给客户
+ //检查更新
+ val sysInfo = SysInfo(
+ BuildConfig.token, VersionInfoUtils.getIMEI(),
+ VersionInfoUtils.getSystemVersion(), "JX_Dev", "JX_Default", null
+ )
+ //如果这里面不为null
+ /**
+ * public interface IUpdateInfo {
+ * String getReleaseNote(); //获取更新包升级信息
+ * String getTargetVersion(); //获取目标版本号
+ * long getSize(); //获取升级包大小
+ * }
+ */
+ val check: IUpdateInfo = client.check(sysInfo)
+
+
+ //下载
+ client.download(object : IDownloadEvent {
+ override fun onDownloading(downloaded: Long, total: Long, extra: Bundle?) {
+ //下载中
+ }
+
+ override fun onDownloaded(extra: Bundle?) {
+ //下载完成
+ }
+
+ /**
+ * 0-停止下载
+ * 1-连接错误
+ * 2-下载中断
+ * 3-下载文件校验失败
+ * 4-其他错误
+ * 5-文件校验失败
+ */
+ override fun onError(errCode: Int, extra: Bundle?) {
+ //下载出错
+ }
+
+ override fun onRetry(p0: Int) {
+
+ }
+
+ })
+
+ //停止下载
+ client.stopDownload()
+
+ //安装
+ //返回值-根据升级方法的实现,该方法可能无法返回。默认返回值如下:
+ //-101-设备调用重启时发生异常
+ //-1001-一般错误
+ //-1004-升级包 MD5 校验失败
+ //其他值-IHookInstall.installPackage 返回值
+ //启动升级包安装流程,同步接口,需要在非主线程执行
+ val install = client.install()
+
+ //通知服务器安装结果
+ //TODO:
+
+ //success-升级是否成功
+ //code-升级失败时候的错误码,默认传 0
+ client.reportUpdateResult(true, 0)
+
+ //curVer-当前版本号
+ client.reportUpdateResultByVersion(VersionInfoUtils.getSystemVersion())
+
+
+ }
+}
\ No newline at end of file
--- /dev/null
+package com.ic.icfotaclient.view
+
+import android.content.Intent
+import android.os.Bundle
+import android.text.TextUtils
+import android.util.Log
+import android.view.View
+import androidx.appcompat.app.AppCompatActivity
+import androidx.lifecycle.Observer
+import com.ic.icfotaclient.R
+import com.ic.icfotaclient.presenter.OtaService
+import com.ic.icfotaclient.presenter.OtaViewModel
+import com.ic.icfotaclient.utils.Constants
+import com.ic.icfotaclient.utils.OtaViewModelCreator
+import com.ic.icfotaclient.utils.UpdateActionState
+import com.ic.icfotaclient.utils.VersionCheckFrom
+import kotlinx.android.synthetic.main.activity_main.*
+
+class SystemUpdateActivity : AppCompatActivity() {
+
+ override fun onBackPressed() {
+ //不让退出了哇
+ val checkNetWork = OtaViewModelCreator.getOtaViewModel(null)?.checkNetWork()
+ val currentActionState = OtaViewModelCreator.getOtaViewModel(null)?.currentActionState
+ val tipsWifiConnect = OtaViewModelCreator.getOtaViewModel(null)?.isTipsWifiConnect
+ if (checkNetWork != true ||
+ currentActionState?.value == UpdateActionState.LAST_VERSION
+ || tipsWifiConnect?.value == false
+ ) {
+ super.onBackPressed()
+ }
+ }
+
+ override fun onResume() {
+ super.onResume()
+ //检查一下网络更新
+ val otaViewModel = OtaViewModelCreator.getOtaViewModel(null)
+ otaViewModel?.checkNetWork()
+ }
+
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+ setContentView(R.layout.activity_main)
+ val intent = Intent(this, OtaService::class.java)
+ intent.action = Constants.CMD_ACTION_ACTIVITY_START
+ startService(intent)
+ checkUpdate.setOnClickListener {
+ val otaViewModel = OtaViewModelCreator.getOtaViewModel(null)
+ if (otaViewModel != null) {
+ //判断状态,执行不同的动作
+ when (otaViewModel.currentActionState.value) {
+ UpdateActionState.NONE -> {
+ //显示检查更新,isEnable=true
+ //检查更新
+ otaViewModel.checkUpdate(VersionCheckFrom.UI_CHECK)
+ }
+ UpdateActionState.NEW_VERSION -> {
+ //去下载
+ otaViewModel.downloadPackage()
+ }
+ UpdateActionState.DOWNLOADED -> {
+ //去安装
+ otaViewModel.install()
+ }
+ UpdateActionState.DOWNLOADING -> {
+ //暂停下载
+ otaViewModel.pauseDownload()
+ }
+ UpdateActionState.DOWNLOAD_PAUSE -> {
+ //去下载
+ otaViewModel.downloadPackage()
+ }
+ else -> {
+ //NONE
+ }
+ }
+ }
+ }
+
+ OtaViewModelCreator.getOtaViewModel(
+ object : OtaViewModelCreator.OnViewModelInitListener {
+ override fun onViewModelInit(otaViewModel: OtaViewModel?) {
+ initObserver(otaViewModel)
+ }
+ })
+ }
+
+ private fun initObserver(otaViewModel: OtaViewModel?) {
+ otaViewModel?.apply {
+ val that = this@SystemUpdateActivity
+ otaInfo.observe(that, Observer {
+ if (it != null) {
+ updateText.text = getString(R.string.update_tips_text) + it.updateDesc
+
+ }
+ })
+ isTipsWifiConnect.observe(that, Observer {
+ if (it) {
+ checkUpdate.text = getString(R.string.tips_wifi_connect)
+ checkUpdate.isEnabled = false
+ }
+ })
+ currentActionState.observe(that, Observer { it ->
+ Log.d("SystemUpdateActivity", "current state == > $it")
+ when (it) {
+ UpdateActionState.NONE -> {
+ //显示检查更新,isEnable=true
+ checkUpdate.text = getString(R.string.check_has_update_text)
+ checkUpdate.isEnabled = true
+ }
+ UpdateActionState.CHECKING -> {
+ checkUpdate.text = getString(R.string.try_to_checking)
+ checkUpdate.isEnabled = false
+ }
+ UpdateActionState.NEW_VERSION -> {
+ checkUpdate.text = getString(R.string.download_package)
+ checkUpdate.isEnabled = true
+ }
+ UpdateActionState.LAST_VERSION -> {
+ checkUpdate.text = getString(R.string.last_version_text)
+ checkUpdate.isEnabled = false
+ }
+ UpdateActionState.DOWNLOADING -> {
+ checkUpdate.text = getString(R.string.pause_download_text)
+ checkUpdate.isEnabled = true
+ }
+ UpdateActionState.DOWNLOAD_PAUSE -> {
+ checkUpdate.text = getString(R.string.continue_download_text)
+ checkUpdate.isEnabled = true
+ }
+ UpdateActionState.DOWNLOADED -> {
+ checkUpdate.text = getString(R.string.update_text)
+ checkUpdate.isEnabled = true
+ }
+ UpdateActionState.NO_NETWORK -> {
+ checkUpdate.text = getString(R.string.no_network_text)
+ checkUpdate.isEnabled = false
+ }
+ UpdateActionState.BATTERY_LEVEL_NOT_ENOUGH -> {
+ checkUpdate.text = getString(R.string.battery_level_tips_text)
+ checkUpdate.isEnabled = false
+ }
+ UpdateActionState.ERROR -> {
+ checkUpdate.text = getString(R.string.error_tips_text)
+ checkUpdate.isEnabled = false
+ }
+ else -> {
+ //NONE
+ }
+ }
+ })
+ downloadProgress.observe(that, Observer {
+ downloadProgressBar.visibility = View.VISIBLE
+ //更新进度条,此为百分比,转成int,max=100
+ downloadProgressBar.progress = it
+ })
+ }
+ val currentVersionCode = otaViewModel?.getCurrentVersionCode()
+ if (!TextUtils.isEmpty(currentVersionCode)) {
+ versionTitle.visibility = View.VISIBLE
+ versionTitle.text = getString(R.string.version_tips) + currentVersionCode
+ }
+ val imeiCode = otaViewModel?.getImeiCode()
+ if (!TextUtils.isEmpty(imeiCode)) {
+ imeiCodeTv.visibility = View.VISIBLE
+ imeiCodeTv.text = getString(R.string.imei_text) + imeiCode
+ }
+
+ }
+}
\ No newline at end of file
--- /dev/null
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:aapt="http://schemas.android.com/aapt"
+ android:width="108dp"
+ android:height="108dp"
+ android:viewportWidth="108"
+ android:viewportHeight="108">
+ <path android:pathData="M31,63.928c0,0 6.4,-11 12.1,-13.1c7.2,-2.6 26,-1.4 26,-1.4l38.1,38.1L107,108.928l-32,-1L31,63.928z">
+ <aapt:attr name="android:fillColor">
+ <gradient
+ android:endX="85.84757"
+ android:endY="92.4963"
+ android:startX="42.9492"
+ android:startY="49.59793"
+ android:type="linear">
+ <item
+ android:color="#44000000"
+ android:offset="0.0" />
+ <item
+ android:color="#00000000"
+ android:offset="1.0" />
+ </gradient>
+ </aapt:attr>
+ </path>
+ <path
+ android:fillColor="#FFFFFF"
+ android:fillType="nonZero"
+ android:pathData="M65.3,45.828l3.8,-6.6c0.2,-0.4 0.1,-0.9 -0.3,-1.1c-0.4,-0.2 -0.9,-0.1 -1.1,0.3l-3.9,6.7c-6.3,-2.8 -13.4,-2.8 -19.7,0l-3.9,-6.7c-0.2,-0.4 -0.7,-0.5 -1.1,-0.3C38.8,38.328 38.7,38.828 38.9,39.228l3.8,6.6C36.2,49.428 31.7,56.028 31,63.928h46C76.3,56.028 71.8,49.428 65.3,45.828zM43.4,57.328c-0.8,0 -1.5,-0.5 -1.8,-1.2c-0.3,-0.7 -0.1,-1.5 0.4,-2.1c0.5,-0.5 1.4,-0.7 2.1,-0.4c0.7,0.3 1.2,1 1.2,1.8C45.3,56.528 44.5,57.328 43.4,57.328L43.4,57.328zM64.6,57.328c-0.8,0 -1.5,-0.5 -1.8,-1.2s-0.1,-1.5 0.4,-2.1c0.5,-0.5 1.4,-0.7 2.1,-0.4c0.7,0.3 1.2,1 1.2,1.8C66.5,56.528 65.6,57.328 64.6,57.328L64.6,57.328z"
+ android:strokeWidth="1"
+ android:strokeColor="#00000000" />
+</vector>
\ No newline at end of file
--- /dev/null
+<?xml version="1.0" encoding="utf-8"?>
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="108dp"
+ android:height="108dp"
+ android:viewportWidth="108"
+ android:viewportHeight="108">
+ <path
+ android:fillColor="#3DDC84"
+ android:pathData="M0,0h108v108h-108z" />
+ <path
+ android:fillColor="#00000000"
+ android:pathData="M9,0L9,108"
+ android:strokeWidth="0.8"
+ android:strokeColor="#33FFFFFF" />
+ <path
+ android:fillColor="#00000000"
+ android:pathData="M19,0L19,108"
+ android:strokeWidth="0.8"
+ android:strokeColor="#33FFFFFF" />
+ <path
+ android:fillColor="#00000000"
+ android:pathData="M29,0L29,108"
+ android:strokeWidth="0.8"
+ android:strokeColor="#33FFFFFF" />
+ <path
+ android:fillColor="#00000000"
+ android:pathData="M39,0L39,108"
+ android:strokeWidth="0.8"
+ android:strokeColor="#33FFFFFF" />
+ <path
+ android:fillColor="#00000000"
+ android:pathData="M49,0L49,108"
+ android:strokeWidth="0.8"
+ android:strokeColor="#33FFFFFF" />
+ <path
+ android:fillColor="#00000000"
+ android:pathData="M59,0L59,108"
+ android:strokeWidth="0.8"
+ android:strokeColor="#33FFFFFF" />
+ <path
+ android:fillColor="#00000000"
+ android:pathData="M69,0L69,108"
+ android:strokeWidth="0.8"
+ android:strokeColor="#33FFFFFF" />
+ <path
+ android:fillColor="#00000000"
+ android:pathData="M79,0L79,108"
+ android:strokeWidth="0.8"
+ android:strokeColor="#33FFFFFF" />
+ <path
+ android:fillColor="#00000000"
+ android:pathData="M89,0L89,108"
+ android:strokeWidth="0.8"
+ android:strokeColor="#33FFFFFF" />
+ <path
+ android:fillColor="#00000000"
+ android:pathData="M99,0L99,108"
+ android:strokeWidth="0.8"
+ android:strokeColor="#33FFFFFF" />
+ <path
+ android:fillColor="#00000000"
+ android:pathData="M0,9L108,9"
+ android:strokeWidth="0.8"
+ android:strokeColor="#33FFFFFF" />
+ <path
+ android:fillColor="#00000000"
+ android:pathData="M0,19L108,19"
+ android:strokeWidth="0.8"
+ android:strokeColor="#33FFFFFF" />
+ <path
+ android:fillColor="#00000000"
+ android:pathData="M0,29L108,29"
+ android:strokeWidth="0.8"
+ android:strokeColor="#33FFFFFF" />
+ <path
+ android:fillColor="#00000000"
+ android:pathData="M0,39L108,39"
+ android:strokeWidth="0.8"
+ android:strokeColor="#33FFFFFF" />
+ <path
+ android:fillColor="#00000000"
+ android:pathData="M0,49L108,49"
+ android:strokeWidth="0.8"
+ android:strokeColor="#33FFFFFF" />
+ <path
+ android:fillColor="#00000000"
+ android:pathData="M0,59L108,59"
+ android:strokeWidth="0.8"
+ android:strokeColor="#33FFFFFF" />
+ <path
+ android:fillColor="#00000000"
+ android:pathData="M0,69L108,69"
+ android:strokeWidth="0.8"
+ android:strokeColor="#33FFFFFF" />
+ <path
+ android:fillColor="#00000000"
+ android:pathData="M0,79L108,79"
+ android:strokeWidth="0.8"
+ android:strokeColor="#33FFFFFF" />
+ <path
+ android:fillColor="#00000000"
+ android:pathData="M0,89L108,89"
+ android:strokeWidth="0.8"
+ android:strokeColor="#33FFFFFF" />
+ <path
+ android:fillColor="#00000000"
+ android:pathData="M0,99L108,99"
+ android:strokeWidth="0.8"
+ android:strokeColor="#33FFFFFF" />
+ <path
+ android:fillColor="#00000000"
+ android:pathData="M19,29L89,29"
+ android:strokeWidth="0.8"
+ android:strokeColor="#33FFFFFF" />
+ <path
+ android:fillColor="#00000000"
+ android:pathData="M19,39L89,39"
+ android:strokeWidth="0.8"
+ android:strokeColor="#33FFFFFF" />
+ <path
+ android:fillColor="#00000000"
+ android:pathData="M19,49L89,49"
+ android:strokeWidth="0.8"
+ android:strokeColor="#33FFFFFF" />
+ <path
+ android:fillColor="#00000000"
+ android:pathData="M19,59L89,59"
+ android:strokeWidth="0.8"
+ android:strokeColor="#33FFFFFF" />
+ <path
+ android:fillColor="#00000000"
+ android:pathData="M19,69L89,69"
+ android:strokeWidth="0.8"
+ android:strokeColor="#33FFFFFF" />
+ <path
+ android:fillColor="#00000000"
+ android:pathData="M19,79L89,79"
+ android:strokeWidth="0.8"
+ android:strokeColor="#33FFFFFF" />
+ <path
+ android:fillColor="#00000000"
+ android:pathData="M29,19L29,89"
+ android:strokeWidth="0.8"
+ android:strokeColor="#33FFFFFF" />
+ <path
+ android:fillColor="#00000000"
+ android:pathData="M39,19L39,89"
+ android:strokeWidth="0.8"
+ android:strokeColor="#33FFFFFF" />
+ <path
+ android:fillColor="#00000000"
+ android:pathData="M49,19L49,89"
+ android:strokeWidth="0.8"
+ android:strokeColor="#33FFFFFF" />
+ <path
+ android:fillColor="#00000000"
+ android:pathData="M59,19L59,89"
+ android:strokeWidth="0.8"
+ android:strokeColor="#33FFFFFF" />
+ <path
+ android:fillColor="#00000000"
+ android:pathData="M69,19L69,89"
+ android:strokeWidth="0.8"
+ android:strokeColor="#33FFFFFF" />
+ <path
+ android:fillColor="#00000000"
+ android:pathData="M79,19L79,89"
+ android:strokeWidth="0.8"
+ android:strokeColor="#33FFFFFF" />
+</vector>
--- /dev/null
+<?xml version="1.0" encoding="utf-8"?>
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+ <item android:drawable="@drawable/shape_btn_disable" android:state_enabled="false" />
+ <item android:drawable="@drawable/shape_btn_press" android:state_pressed="true" />
+ <item android:drawable="@drawable/shape_btn_normal" android:state_enabled="true" />
+ <item android:drawable="@drawable/shape_btn_normal" />
+
+</selector>
\ No newline at end of file
--- /dev/null
+<?xml version="1.0" encoding="utf-8"?>
+<shape xmlns:android="http://schemas.android.com/apk/res/android"
+ android:shape="rectangle">
+
+ <corners android:radius="5dp" />
+ <solid android:color="#6a6a6a" />
+
+ <size
+ android:width="200dp"
+ android:height="40dp" />
+
+</shape>
\ No newline at end of file
--- /dev/null
+<?xml version="1.0" encoding="utf-8"?>
+<shape xmlns:android="http://schemas.android.com/apk/res/android"
+ android:shape="rectangle">
+
+ <corners android:radius="5dp" />
+ <solid android:color="#00aaff" />
+
+ <size
+ android:width="200dp"
+ android:height="40dp" />
+
+</shape>
\ No newline at end of file
--- /dev/null
+<?xml version="1.0" encoding="utf-8"?>
+<shape xmlns:android="http://schemas.android.com/apk/res/android"
+ android:shape="rectangle">
+
+ <corners android:radius="5dp" />
+ <solid android:color="#0044ff" />
+
+ <size
+ android:width="200dp"
+ android:height="40dp" />
+
+</shape>
\ No newline at end of file
--- /dev/null
+<?xml version="1.0" encoding="utf-8"?>
+<androidx.constraintlayout.widget.ConstraintLayout
+ xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent"
+ android:layout_height="match_parent">
+
+</androidx.constraintlayout.widget.ConstraintLayout>
\ No newline at end of file
--- /dev/null
+<?xml version="1.0" encoding="utf-8"?>
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:app="http://schemas.android.com/apk/res-auto"
+ xmlns:tools="http://schemas.android.com/tools"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:gravity="center_horizontal"
+ android:orientation="vertical"
+ android:padding="5dp"
+ tools:context=".view.SystemUpdateActivity">
+
+ <ScrollView
+ android:id="@+id/text_container"
+ android:layout_width="match_parent"
+ android:layout_height="0dp"
+ android:layout_weight="1">
+
+
+ <LinearLayout
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:layout_marginTop="24dp"
+ android:orientation="vertical"
+ app:layout_constraintTop_toTopOf="parent"
+ tools:layout_editor_absoluteX="5dp">
+
+ <!--IMEI code-->
+ <TextView
+ android:id="@+id/imeiCodeTv"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:gravity="left"
+ android:textColor="@color/black"
+ android:textSize="18sp"
+ app:layout_constraintTop_toTopOf="parent"
+ tools:layout_editor_absoluteX="5dp" />
+
+ <!--current version code.-->
+ <TextView
+ android:id="@+id/versionTitle"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:layout_marginBottom="20dp"
+ android:gravity="left"
+ android:textColor="@color/black"
+ android:textSize="18sp" />
+
+ <TextView
+ android:id="@+id/updateText"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:textColor="@color/black"
+ android:textSize="18sp" />
+ </LinearLayout>
+
+ </ScrollView>
+ <!--下载进度条-->
+ <ProgressBar
+ android:id="@+id/downloadProgressBar"
+ style="?android:attr/progressBarStyleHorizontal"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:layout_below="@id/text_container"
+ android:layout_gravity="center_vertical"
+ android:layout_marginTop="20dp"
+ android:layout_marginBottom="20dp"
+ android:max="100"
+ android:visibility="gone" />
+
+ <!--检查是否有更新-->
+ <TextView
+ android:id="@+id/checkUpdate"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_below="@id/downloadProgressBar"
+ android:layout_marginBottom="20dp"
+ android:background="@drawable/selector_btn_bg"
+ android:gravity="center"
+ android:padding="10dp"
+ android:text="@string/check_has_update_text"
+ android:textColor="@color/white"
+ android:textSize="24sp" />
+
+</LinearLayout>
\ No newline at end of file
--- /dev/null
+<?xml version="1.0" encoding="utf-8"?>
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:background="@color/grey"
+ android:gravity="center_horizontal"
+ android:orientation="vertical"
+ android:padding="4dp">
+
+
+ <TextView
+ android:id="@+id/notify"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_marginTop="5dp"
+ android:gravity="center_vertical|center_horizontal"
+ android:text="有新版本更新啦!"
+ android:textAppearance="?android:attr/textAppearanceMedium"
+ android:textColor="@color/white"
+ android:textIsSelectable="true" />
+
+
+ <ScrollView
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:paddingTop="5dp"
+ android:paddingBottom="5dp">
+
+ <TextView
+ android:id="@+id/description"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:gravity="left"
+ android:padding="4dp"
+ android:textAppearance="?android:attr/textAppearanceMedium"
+ android:textColor="@color/white"
+ android:textIsSelectable="true" />
+ </ScrollView>
+
+ <View
+ android:layout_width="200dp"
+ android:layout_height="1px"
+ android:background="@color/white" />
+
+ <!-- Alert dialog style buttons along the bottom. -->
+ <LinearLayout
+ style="?android:attr/buttonBarStyle"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:measureWithLargestChild="true">
+
+ <Button
+ android:id="@+id/button_cancel"
+ style="?android:attr/buttonBarButtonStyle"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_weight="1"
+ android:text="@string/IFIA_btn_no"
+ android:textColor="@color/white" />
+
+ <Button
+ android:id="@+id/button_ok"
+ style="?android:attr/buttonBarButtonStyle"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_weight="1"
+ android:text="@string/IFIA_btn_yes"
+ android:textColor="@color/white" />
+ </LinearLayout>
+</LinearLayout>
--- /dev/null
+<?xml version="1.0" encoding="utf-8"?>
+<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
+ <background android:drawable="@drawable/ic_launcher_background" />
+ <foreground android:drawable="@drawable/ic_launcher_foreground" />
+</adaptive-icon>
\ No newline at end of file
--- /dev/null
+<?xml version="1.0" encoding="utf-8"?>
+<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
+ <background android:drawable="@drawable/ic_launcher_background" />
+ <foreground android:drawable="@drawable/ic_launcher_foreground" />
+</adaptive-icon>
\ No newline at end of file
--- /dev/null
+<?xml version="1.0" encoding="utf-8"?>
+<resources>
+ <color name="purple_200">#FFBB86FC</color>
+ <color name="purple_500">#FF6200EE</color>
+ <color name="purple_700">#FF3700B3</color>
+ <color name="teal_200">#FF03DAC5</color>
+ <color name="teal_700">#FF018786</color>
+ <color name="black">#FF000000</color>
+ <color name="white">#FFFFFFFF</color>
+ <color name="grey">#6d6d6d</color>
+</resources>
\ No newline at end of file
--- /dev/null
+<resources>
+ <string name="app_name">ICFotaClient</string>
+ <string name="IFIA_btn_yes">是</string>
+ <string name="IFIA_btn_no">否</string>
+ <string name="pb_title_downloading">正在下载升级包</string>
+ <string name="check_has_update_text">检查是否有更新</string>
+ <string name="update_text">马上升级</string>
+ <string name="no_network_text">无法触及到服务器</string>
+ <string name="battery_level_tips_text">请确保电量50%以上</string>
+ <string name="error_tips_text">资源准备失败,请重启尝试</string>
+ <string name="last_version_text">已经是最新版本</string>
+ <string name="download_package">下载更新包</string>
+ <string name="try_to_downloading">正在玩命下载中…</string>
+ <string name="pause_download_text">暂停下载</string>
+ <string name="continue_download_text">继续下载</string>
+ <string name="try_to_checking">正在玩命检查新版本…</string>
+ <string name="version_tips">当前版本:</string>
+ <string name="imei_text">IMEI号:</string>
+ <string name="update_tips_text">更新说明:\n</string>
+ <string name="tips_wifi_connect">请连接WI-FI下载</string>
+ <string name="unsupport_pause_download">该服务器不支持暂停下载</string>
+</resources>
\ No newline at end of file
--- /dev/null
+<resources xmlns:tools="http://schemas.android.com/tools">
+ <!-- Base application theme. -->
+ <style name="Theme.ICFotaClient" parent="Theme.MaterialComponents.DayNight.NoActionBar">
+ <!-- Primary brand color. -->
+ <item name="colorPrimary">@color/purple_500</item>
+ <item name="colorPrimaryVariant">@color/purple_700</item>
+ <item name="colorOnPrimary">@color/white</item>
+ <!-- Secondary brand color. -->
+ <item name="colorSecondary">@color/teal_200</item>
+ <item name="colorSecondaryVariant">@color/teal_700</item>
+ <item name="colorOnSecondary">@color/black</item>
+ <!-- Status bar color. -->
+ <item name="android:statusBarColor" tools:targetApi="l">?attr/colorPrimaryVariant</item>
+ <!-- Customize your theme here. -->
+ </style>
+</resources>
\ No newline at end of file
--- /dev/null
+package com.ic.icfotaclient
+
+import org.junit.Test
+
+import org.junit.Assert.*
+
+/**
+ * Example local unit test, which will execute on the development machine (host).
+ *
+ * See [testing documentation](http://d.android.com/tools/testing).
+ */
+class ExampleUnitTest {
+ @Test
+ fun addition_isCorrect() {
+ assertEquals(4, 2 + 2)
+ }
+}
\ No newline at end of file
--- /dev/null
+#Thu Jul 16 16:46:05 CST 2020
+VERSION_CODE=1
--- /dev/null
+// Top-level build file where you can add configuration options common to all sub-projects/modules.
+buildscript {
+ ext.kotlin_version = "1.4.21"
+ repositories {
+ maven { url 'http://maven.aliyun.com/nexus/content/groups/public/' }
+ maven { url 'http://maven.aliyun.com/nexus/content/repositories/jcenter' }
+ google()
+ jcenter()
+ }
+ dependencies {
+ classpath "com.android.tools.build:gradle:4.1.1"
+ classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
+
+ // NOTE: Do not place your application dependencies here; they belong
+ // in the individual module build.gradle files
+ }
+}
+
+allprojects {
+ repositories {
+ maven { url 'http://maven.aliyun.com/nexus/content/groups/public/' }
+ maven { url 'http://maven.aliyun.com/nexus/content/repositories/jcenter' }
+ google()
+ jcenter()
+ }
+}
+
+task clean(type: Delete) {
+ delete rootProject.buildDir
+}
\ No newline at end of file
--- /dev/null
+# Project-wide Gradle settings.
+# IDE (e.g. Android Studio) users:
+# Gradle settings configured through the IDE *will override*
+# any settings specified in this file.
+# For more details on how to configure your build environment visit
+# http://www.gradle.org/docs/current/userguide/build_environment.html
+# Specifies the JVM arguments used for the daemon process.
+# The setting is particularly useful for tweaking memory settings.
+org.gradle.jvmargs=-Xmx2048m -Dfile.encoding=UTF-8
+# When configured, Gradle will run in incubating parallel mode.
+# This option should only be used with decoupled projects. More details, visit
+# http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
+# org.gradle.parallel=true
+# AndroidX package structure to make it clearer which packages are bundled with the
+# Android operating system, and which are packaged with your app"s APK
+# https://developer.android.com/topic/libraries/support-library/androidx-rn
+android.useAndroidX=true
+# Automatically convert third-party libraries to use AndroidX
+android.enableJetifier=true
+# Kotlin code style for this project: "official" or "obsolete":
+kotlin.code.style=official
\ No newline at end of file
--- /dev/null
+#Fri Jan 08 09:56:30 CST 2021
+distributionBase=GRADLE_USER_HOME
+distributionPath=wrapper/dists
+zipStoreBase=GRADLE_USER_HOME
+zipStorePath=wrapper/dists
+distributionUrl=https\://services.gradle.org/distributions/gradle-6.5-bin.zip
--- /dev/null
+#!/usr/bin/env sh
+
+##############################################################################
+##
+## Gradle start up script for UN*X
+##
+##############################################################################
+
+# Attempt to set APP_HOME
+# Resolve links: $0 may be a link
+PRG="$0"
+# Need this for relative symlinks.
+while [ -h "$PRG" ] ; do
+ ls=`ls -ld "$PRG"`
+ link=`expr "$ls" : '.*-> \(.*\)$'`
+ if expr "$link" : '/.*' > /dev/null; then
+ PRG="$link"
+ else
+ PRG=`dirname "$PRG"`"/$link"
+ fi
+done
+SAVED="`pwd`"
+cd "`dirname \"$PRG\"`/" >/dev/null
+APP_HOME="`pwd -P`"
+cd "$SAVED" >/dev/null
+
+APP_NAME="Gradle"
+APP_BASE_NAME=`basename "$0"`
+
+# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
+DEFAULT_JVM_OPTS=""
+
+# Use the maximum available, or set MAX_FD != -1 to use that value.
+MAX_FD="maximum"
+
+warn () {
+ echo "$*"
+}
+
+die () {
+ echo
+ echo "$*"
+ echo
+ exit 1
+}
+
+# OS specific support (must be 'true' or 'false').
+cygwin=false
+msys=false
+darwin=false
+nonstop=false
+case "`uname`" in
+ CYGWIN* )
+ cygwin=true
+ ;;
+ Darwin* )
+ darwin=true
+ ;;
+ MINGW* )
+ msys=true
+ ;;
+ NONSTOP* )
+ nonstop=true
+ ;;
+esac
+
+CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
+
+# Determine the Java command to use to start the JVM.
+if [ -n "$JAVA_HOME" ] ; then
+ if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
+ # IBM's JDK on AIX uses strange locations for the executables
+ JAVACMD="$JAVA_HOME/jre/sh/java"
+ else
+ JAVACMD="$JAVA_HOME/bin/java"
+ fi
+ if [ ! -x "$JAVACMD" ] ; then
+ die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
+
+Please set the JAVA_HOME variable in your environment to match the
+location of your Java installation."
+ fi
+else
+ JAVACMD="java"
+ which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
+
+Please set the JAVA_HOME variable in your environment to match the
+location of your Java installation."
+fi
+
+# Increase the maximum file descriptors if we can.
+if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then
+ MAX_FD_LIMIT=`ulimit -H -n`
+ if [ $? -eq 0 ] ; then
+ if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
+ MAX_FD="$MAX_FD_LIMIT"
+ fi
+ ulimit -n $MAX_FD
+ if [ $? -ne 0 ] ; then
+ warn "Could not set maximum file descriptor limit: $MAX_FD"
+ fi
+ else
+ warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
+ fi
+fi
+
+# For Darwin, add options to specify how the application appears in the dock
+if $darwin; then
+ GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
+fi
+
+# For Cygwin, switch paths to Windows format before running java
+if $cygwin ; then
+ APP_HOME=`cygpath --path --mixed "$APP_HOME"`
+ CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
+ JAVACMD=`cygpath --unix "$JAVACMD"`
+
+ # We build the pattern for arguments to be converted via cygpath
+ ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
+ SEP=""
+ for dir in $ROOTDIRSRAW ; do
+ ROOTDIRS="$ROOTDIRS$SEP$dir"
+ SEP="|"
+ done
+ OURCYGPATTERN="(^($ROOTDIRS))"
+ # Add a user-defined pattern to the cygpath arguments
+ if [ "$GRADLE_CYGPATTERN" != "" ] ; then
+ OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
+ fi
+ # Now convert the arguments - kludge to limit ourselves to /bin/sh
+ i=0
+ for arg in "$@" ; do
+ CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
+ CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
+
+ if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
+ eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
+ else
+ eval `echo args$i`="\"$arg\""
+ fi
+ i=$((i+1))
+ done
+ case $i in
+ (0) set -- ;;
+ (1) set -- "$args0" ;;
+ (2) set -- "$args0" "$args1" ;;
+ (3) set -- "$args0" "$args1" "$args2" ;;
+ (4) set -- "$args0" "$args1" "$args2" "$args3" ;;
+ (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
+ (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
+ (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
+ (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
+ (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
+ esac
+fi
+
+# Escape application args
+save () {
+ for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done
+ echo " "
+}
+APP_ARGS=$(save "$@")
+
+# Collect all arguments for the java command, following the shell quoting and substitution rules
+eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS"
+
+# by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong
+if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then
+ cd "$(dirname "$0")"
+fi
+
+exec "$JAVACMD" "$@"
--- /dev/null
+@if "%DEBUG%" == "" @echo off
+@rem ##########################################################################
+@rem
+@rem Gradle startup script for Windows
+@rem
+@rem ##########################################################################
+
+@rem Set local scope for the variables with windows NT shell
+if "%OS%"=="Windows_NT" setlocal
+
+set DIRNAME=%~dp0
+if "%DIRNAME%" == "" set DIRNAME=.
+set APP_BASE_NAME=%~n0
+set APP_HOME=%DIRNAME%
+
+@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
+set DEFAULT_JVM_OPTS=
+
+@rem Find java.exe
+if defined JAVA_HOME goto findJavaFromJavaHome
+
+set JAVA_EXE=java.exe
+%JAVA_EXE% -version >NUL 2>&1
+if "%ERRORLEVEL%" == "0" goto init
+
+echo.
+echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
+echo.
+echo Please set the JAVA_HOME variable in your environment to match the
+echo location of your Java installation.
+
+goto fail
+
+:findJavaFromJavaHome
+set JAVA_HOME=%JAVA_HOME:"=%
+set JAVA_EXE=%JAVA_HOME%/bin/java.exe
+
+if exist "%JAVA_EXE%" goto init
+
+echo.
+echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
+echo.
+echo Please set the JAVA_HOME variable in your environment to match the
+echo location of your Java installation.
+
+goto fail
+
+:init
+@rem Get command-line arguments, handling Windows variants
+
+if not "%OS%" == "Windows_NT" goto win9xME_args
+
+:win9xME_args
+@rem Slurp the command line arguments.
+set CMD_LINE_ARGS=
+set _SKIP=2
+
+:win9xME_args_slurp
+if "x%~1" == "x" goto execute
+
+set CMD_LINE_ARGS=%*
+
+:execute
+@rem Setup the command line
+
+set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
+
+@rem Execute Gradle
+"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
+
+:end
+@rem End local scope for the variables with windows NT shell
+if "%ERRORLEVEL%"=="0" goto mainEnd
+
+:fail
+rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
+rem the _cmd.exe /c_ return code!
+if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
+exit /b 1
+
+:mainEnd
+if "%OS%"=="Windows_NT" endlocal
+
+:omega
--- /dev/null
+include ':app'
+rootProject.name = "ICFotaClient"
\ No newline at end of file