[
  {
    "path": ".github/issue_template.md",
    "content": "#### Before Issue\n\n1. Please search on the [Issues](https://github.com/lingochamp/FileDownloader/issues)\n2. Please search on the [wiki](https://github.com/lingochamp/FileDownloader/wiki)\n3. Please set `FileDownloadLog.NEED_LOG=true` and review the Logcat output from main process and `:filedownloader` process ( pay attention to Warn and Error level logcat)\n\n#### Issue\n\n1. What problem do you get?\n2. Which version of FileDownloader are you using when you produce such problem?\n3. How to reproduce such problem?\n4. Do you set `FileDownloadLog.NEED_LOG=true`?\n5. Could you please reproduce this problem and provide all main process and `:filedownloader` process logcat \n6. Can you fix it by yourself and request PR, if not, what's problem do you get when you try to fix it\n\n>P.S. If you don't know how to get `:filedownloader` process, it's recommended to using `pidcat` to just filter all your application logcat, or define `process.non-separate=true` on [filedownloader.properties](https://github.com/lingochamp/FileDownloader/wiki/filedownloader.properties)\n\n---\n\n请在Issue前认真的跟进上面提到的建议，这样将可以极大的加快你遇到问题的处理。\n"
  },
  {
    "path": ".gitignore",
    "content": "*.iml\n.gradle\n/local.properties\n/.idea/workspace.xml\n/.idea/libraries\n/.idea/misc.xml\n/.idea/checkstyle-idea.xml\n/.idea/caches\n.DS_Store\n/build\n/captures\n.classpath\n.project\n.settings\n.vscode\n"
  },
  {
    "path": ".idea/codeStyleSettings.xml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<project version=\"4\">\n  <component name=\"ProjectCodeStyleSettingsManager\">\n    <option name=\"PER_PROJECT_SETTINGS\">\n      <value>\n        <option name=\"CLASS_COUNT_TO_USE_IMPORT_ON_DEMAND\" value=\"99\" />\n        <option name=\"NAMES_COUNT_TO_USE_IMPORT_ON_DEMAND\" value=\"99\" />\n        <option name=\"PACKAGES_TO_USE_IMPORT_ON_DEMAND\">\n          <value />\n        </option>\n        <option name=\"IMPORT_LAYOUT_TABLE\">\n          <value>\n            <package name=\"android\" withSubpackages=\"true\" static=\"false\" />\n            <emptyLine />\n            <package name=\"com\" withSubpackages=\"true\" static=\"false\" />\n            <emptyLine />\n            <package name=\"junit\" withSubpackages=\"true\" static=\"false\" />\n            <emptyLine />\n            <package name=\"net\" withSubpackages=\"true\" static=\"false\" />\n            <emptyLine />\n            <package name=\"org\" withSubpackages=\"true\" static=\"false\" />\n            <emptyLine />\n            <package name=\"java\" withSubpackages=\"true\" static=\"false\" />\n            <emptyLine />\n            <package name=\"javax\" withSubpackages=\"true\" static=\"false\" />\n            <emptyLine />\n            <package name=\"\" withSubpackages=\"true\" static=\"false\" />\n            <emptyLine />\n            <package name=\"\" withSubpackages=\"true\" static=\"true\" />\n            <emptyLine />\n          </value>\n        </option>\n        <option name=\"RIGHT_MARGIN\" value=\"100\" />\n        <AndroidXmlCodeStyleSettings>\n          <option name=\"USE_CUSTOM_SETTINGS\" value=\"true\" />\n        </AndroidXmlCodeStyleSettings>\n        <Objective-C-extensions>\n          <file>\n            <option name=\"com.jetbrains.cidr.lang.util.OCDeclarationKind\" value=\"Import\" />\n            <option name=\"com.jetbrains.cidr.lang.util.OCDeclarationKind\" value=\"Macro\" />\n            <option name=\"com.jetbrains.cidr.lang.util.OCDeclarationKind\" value=\"Typedef\" />\n            <option name=\"com.jetbrains.cidr.lang.util.OCDeclarationKind\" value=\"Enum\" />\n            <option name=\"com.jetbrains.cidr.lang.util.OCDeclarationKind\" value=\"Constant\" />\n            <option name=\"com.jetbrains.cidr.lang.util.OCDeclarationKind\" value=\"Global\" />\n            <option name=\"com.jetbrains.cidr.lang.util.OCDeclarationKind\" value=\"Struct\" />\n            <option name=\"com.jetbrains.cidr.lang.util.OCDeclarationKind\" value=\"FunctionPredecl\" />\n            <option name=\"com.jetbrains.cidr.lang.util.OCDeclarationKind\" value=\"Function\" />\n          </file>\n          <class>\n            <option name=\"com.jetbrains.cidr.lang.util.OCDeclarationKind\" value=\"Property\" />\n            <option name=\"com.jetbrains.cidr.lang.util.OCDeclarationKind\" value=\"Synthesize\" />\n            <option name=\"com.jetbrains.cidr.lang.util.OCDeclarationKind\" value=\"InitMethod\" />\n            <option name=\"com.jetbrains.cidr.lang.util.OCDeclarationKind\" value=\"StaticMethod\" />\n            <option name=\"com.jetbrains.cidr.lang.util.OCDeclarationKind\" value=\"InstanceMethod\" />\n            <option name=\"com.jetbrains.cidr.lang.util.OCDeclarationKind\" value=\"DeallocMethod\" />\n          </class>\n          <extensions>\n            <pair source=\"cpp\" header=\"h\" />\n            <pair source=\"c\" header=\"h\" />\n          </extensions>\n        </Objective-C-extensions>\n        <XML>\n          <option name=\"XML_KEEP_LINE_BREAKS\" value=\"false\" />\n          <option name=\"XML_ALIGN_ATTRIBUTES\" value=\"false\" />\n          <option name=\"XML_SPACE_INSIDE_EMPTY_TAG\" value=\"true\" />\n        </XML>\n        <codeStyleSettings language=\"XML\">\n          <option name=\"FORCE_REARRANGE_MODE\" value=\"1\" />\n          <indentOptions>\n            <option name=\"CONTINUATION_INDENT_SIZE\" value=\"4\" />\n          </indentOptions>\n          <arrangement>\n            <rules>\n              <section>\n                <rule>\n                  <match>\n                    <AND>\n                      <NAME>xmlns:android</NAME>\n                      <XML_NAMESPACE>^$</XML_NAMESPACE>\n                    </AND>\n                  </match>\n                </rule>\n              </section>\n              <section>\n                <rule>\n                  <match>\n                    <AND>\n                      <NAME>xmlns:.*</NAME>\n                      <XML_NAMESPACE>^$</XML_NAMESPACE>\n                    </AND>\n                  </match>\n                  <order>BY_NAME</order>\n                </rule>\n              </section>\n              <section>\n                <rule>\n                  <match>\n                    <AND>\n                      <NAME>.*:id</NAME>\n                      <XML_NAMESPACE>http://schemas.android.com/apk/res/android</XML_NAMESPACE>\n                    </AND>\n                  </match>\n                </rule>\n              </section>\n              <section>\n                <rule>\n                  <match>\n                    <AND>\n                      <NAME>.*:name</NAME>\n                      <XML_NAMESPACE>http://schemas.android.com/apk/res/android</XML_NAMESPACE>\n                    </AND>\n                  </match>\n                </rule>\n              </section>\n              <section>\n                <rule>\n                  <match>\n                    <AND>\n                      <NAME>name</NAME>\n                      <XML_NAMESPACE>^$</XML_NAMESPACE>\n                    </AND>\n                  </match>\n                </rule>\n              </section>\n              <section>\n                <rule>\n                  <match>\n                    <AND>\n                      <NAME>style</NAME>\n                      <XML_NAMESPACE>^$</XML_NAMESPACE>\n                    </AND>\n                  </match>\n                </rule>\n              </section>\n              <section>\n                <rule>\n                  <match>\n                    <AND>\n                      <NAME>.*</NAME>\n                      <XML_NAMESPACE>^$</XML_NAMESPACE>\n                    </AND>\n                  </match>\n                  <order>BY_NAME</order>\n                </rule>\n              </section>\n              <section>\n                <rule>\n                  <match>\n                    <AND>\n                      <NAME>.*:layout_width</NAME>\n                      <XML_NAMESPACE>http://schemas.android.com/apk/res/android</XML_NAMESPACE>\n                    </AND>\n                  </match>\n                </rule>\n              </section>\n              <section>\n                <rule>\n                  <match>\n                    <AND>\n                      <NAME>.*:layout_height</NAME>\n                      <XML_NAMESPACE>http://schemas.android.com/apk/res/android</XML_NAMESPACE>\n                    </AND>\n                  </match>\n                </rule>\n              </section>\n              <section>\n                <rule>\n                  <match>\n                    <AND>\n                      <NAME>.*:layout_.*</NAME>\n                      <XML_NAMESPACE>http://schemas.android.com/apk/res/android</XML_NAMESPACE>\n                    </AND>\n                  </match>\n                  <order>BY_NAME</order>\n                </rule>\n              </section>\n              <section>\n                <rule>\n                  <match>\n                    <AND>\n                      <NAME>.*:width</NAME>\n                      <XML_NAMESPACE>http://schemas.android.com/apk/res/android</XML_NAMESPACE>\n                    </AND>\n                  </match>\n                  <order>BY_NAME</order>\n                </rule>\n              </section>\n              <section>\n                <rule>\n                  <match>\n                    <AND>\n                      <NAME>.*:height</NAME>\n                      <XML_NAMESPACE>http://schemas.android.com/apk/res/android</XML_NAMESPACE>\n                    </AND>\n                  </match>\n                  <order>BY_NAME</order>\n                </rule>\n              </section>\n              <section>\n                <rule>\n                  <match>\n                    <AND>\n                      <NAME>.*</NAME>\n                      <XML_NAMESPACE>http://schemas.android.com/apk/res/android</XML_NAMESPACE>\n                    </AND>\n                  </match>\n                  <order>BY_NAME</order>\n                </rule>\n              </section>\n              <section>\n                <rule>\n                  <match>\n                    <AND>\n                      <NAME>.*</NAME>\n                      <XML_NAMESPACE>.*</XML_NAMESPACE>\n                    </AND>\n                  </match>\n                  <order>BY_NAME</order>\n                </rule>\n              </section>\n            </rules>\n          </arrangement>\n        </codeStyleSettings>\n      </value>\n    </option>\n    <option name=\"PREFERRED_PROJECT_CODE_STYLE\" value=\"Default (1)\" />\n  </component>\n</project>"
  },
  {
    "path": ".idea/codeStyles/Project.xml",
    "content": "<component name=\"ProjectCodeStyleConfiguration\">\n  <code_scheme name=\"Project\" version=\"173\">\n    <option name=\"RIGHT_MARGIN\" value=\"100\" />\n    <AndroidXmlCodeStyleSettings>\n      <option name=\"USE_CUSTOM_SETTINGS\" value=\"true\" />\n    </AndroidXmlCodeStyleSettings>\n    <JavaCodeStyleSettings>\n      <option name=\"CLASS_COUNT_TO_USE_IMPORT_ON_DEMAND\" value=\"99\" />\n      <option name=\"NAMES_COUNT_TO_USE_IMPORT_ON_DEMAND\" value=\"99\" />\n      <option name=\"PACKAGES_TO_USE_IMPORT_ON_DEMAND\">\n        <value />\n      </option>\n      <option name=\"IMPORT_LAYOUT_TABLE\">\n        <value>\n          <package name=\"android\" withSubpackages=\"true\" static=\"false\" />\n          <emptyLine />\n          <package name=\"com\" withSubpackages=\"true\" static=\"false\" />\n          <emptyLine />\n          <package name=\"junit\" withSubpackages=\"true\" static=\"false\" />\n          <emptyLine />\n          <package name=\"net\" withSubpackages=\"true\" static=\"false\" />\n          <emptyLine />\n          <package name=\"org\" withSubpackages=\"true\" static=\"false\" />\n          <emptyLine />\n          <package name=\"java\" withSubpackages=\"true\" static=\"false\" />\n          <emptyLine />\n          <package name=\"javax\" withSubpackages=\"true\" static=\"false\" />\n          <emptyLine />\n          <package name=\"\" withSubpackages=\"true\" static=\"false\" />\n          <emptyLine />\n          <package name=\"\" withSubpackages=\"true\" static=\"true\" />\n          <emptyLine />\n        </value>\n      </option>\n    </JavaCodeStyleSettings>\n    <Objective-C-extensions>\n      <file>\n        <option name=\"com.jetbrains.cidr.lang.util.OCDeclarationKind\" value=\"Import\" />\n        <option name=\"com.jetbrains.cidr.lang.util.OCDeclarationKind\" value=\"Macro\" />\n        <option name=\"com.jetbrains.cidr.lang.util.OCDeclarationKind\" value=\"Typedef\" />\n        <option name=\"com.jetbrains.cidr.lang.util.OCDeclarationKind\" value=\"Enum\" />\n        <option name=\"com.jetbrains.cidr.lang.util.OCDeclarationKind\" value=\"Constant\" />\n        <option name=\"com.jetbrains.cidr.lang.util.OCDeclarationKind\" value=\"Global\" />\n        <option name=\"com.jetbrains.cidr.lang.util.OCDeclarationKind\" value=\"Struct\" />\n        <option name=\"com.jetbrains.cidr.lang.util.OCDeclarationKind\" value=\"FunctionPredecl\" />\n        <option name=\"com.jetbrains.cidr.lang.util.OCDeclarationKind\" value=\"Function\" />\n      </file>\n      <class>\n        <option name=\"com.jetbrains.cidr.lang.util.OCDeclarationKind\" value=\"Property\" />\n        <option name=\"com.jetbrains.cidr.lang.util.OCDeclarationKind\" value=\"Synthesize\" />\n        <option name=\"com.jetbrains.cidr.lang.util.OCDeclarationKind\" value=\"InitMethod\" />\n        <option name=\"com.jetbrains.cidr.lang.util.OCDeclarationKind\" value=\"StaticMethod\" />\n        <option name=\"com.jetbrains.cidr.lang.util.OCDeclarationKind\" value=\"InstanceMethod\" />\n        <option name=\"com.jetbrains.cidr.lang.util.OCDeclarationKind\" value=\"DeallocMethod\" />\n      </class>\n      <extensions>\n        <pair source=\"cpp\" header=\"h\" fileNamingConvention=\"NONE\" />\n        <pair source=\"c\" header=\"h\" fileNamingConvention=\"NONE\" />\n      </extensions>\n    </Objective-C-extensions>\n    <XML>\n      <option name=\"XML_KEEP_LINE_BREAKS\" value=\"false\" />\n      <option name=\"XML_ALIGN_ATTRIBUTES\" value=\"false\" />\n      <option name=\"XML_SPACE_INSIDE_EMPTY_TAG\" value=\"true\" />\n    </XML>\n    <codeStyleSettings language=\"XML\">\n      <option name=\"FORCE_REARRANGE_MODE\" value=\"1\" />\n      <indentOptions>\n        <option name=\"CONTINUATION_INDENT_SIZE\" value=\"4\" />\n      </indentOptions>\n      <arrangement>\n        <rules>\n          <section>\n            <rule>\n              <match>\n                <AND>\n                  <NAME>xmlns:android</NAME>\n                  <XML_NAMESPACE>^$</XML_NAMESPACE>\n                </AND>\n              </match>\n            </rule>\n          </section>\n          <section>\n            <rule>\n              <match>\n                <AND>\n                  <NAME>xmlns:.*</NAME>\n                  <XML_NAMESPACE>^$</XML_NAMESPACE>\n                </AND>\n              </match>\n              <order>BY_NAME</order>\n            </rule>\n          </section>\n          <section>\n            <rule>\n              <match>\n                <AND>\n                  <NAME>.*:id</NAME>\n                  <XML_NAMESPACE>http://schemas.android.com/apk/res/android</XML_NAMESPACE>\n                </AND>\n              </match>\n            </rule>\n          </section>\n          <section>\n            <rule>\n              <match>\n                <AND>\n                  <NAME>.*:name</NAME>\n                  <XML_NAMESPACE>http://schemas.android.com/apk/res/android</XML_NAMESPACE>\n                </AND>\n              </match>\n            </rule>\n          </section>\n          <section>\n            <rule>\n              <match>\n                <AND>\n                  <NAME>name</NAME>\n                  <XML_NAMESPACE>^$</XML_NAMESPACE>\n                </AND>\n              </match>\n            </rule>\n          </section>\n          <section>\n            <rule>\n              <match>\n                <AND>\n                  <NAME>style</NAME>\n                  <XML_NAMESPACE>^$</XML_NAMESPACE>\n                </AND>\n              </match>\n            </rule>\n          </section>\n          <section>\n            <rule>\n              <match>\n                <AND>\n                  <NAME>.*</NAME>\n                  <XML_NAMESPACE>^$</XML_NAMESPACE>\n                </AND>\n              </match>\n              <order>BY_NAME</order>\n            </rule>\n          </section>\n          <section>\n            <rule>\n              <match>\n                <AND>\n                  <NAME>.*:layout_width</NAME>\n                  <XML_NAMESPACE>http://schemas.android.com/apk/res/android</XML_NAMESPACE>\n                </AND>\n              </match>\n            </rule>\n          </section>\n          <section>\n            <rule>\n              <match>\n                <AND>\n                  <NAME>.*:layout_height</NAME>\n                  <XML_NAMESPACE>http://schemas.android.com/apk/res/android</XML_NAMESPACE>\n                </AND>\n              </match>\n            </rule>\n          </section>\n          <section>\n            <rule>\n              <match>\n                <AND>\n                  <NAME>.*:layout_.*</NAME>\n                  <XML_NAMESPACE>http://schemas.android.com/apk/res/android</XML_NAMESPACE>\n                </AND>\n              </match>\n              <order>BY_NAME</order>\n            </rule>\n          </section>\n          <section>\n            <rule>\n              <match>\n                <AND>\n                  <NAME>.*:width</NAME>\n                  <XML_NAMESPACE>http://schemas.android.com/apk/res/android</XML_NAMESPACE>\n                </AND>\n              </match>\n              <order>BY_NAME</order>\n            </rule>\n          </section>\n          <section>\n            <rule>\n              <match>\n                <AND>\n                  <NAME>.*:height</NAME>\n                  <XML_NAMESPACE>http://schemas.android.com/apk/res/android</XML_NAMESPACE>\n                </AND>\n              </match>\n              <order>BY_NAME</order>\n            </rule>\n          </section>\n          <section>\n            <rule>\n              <match>\n                <AND>\n                  <NAME>.*</NAME>\n                  <XML_NAMESPACE>http://schemas.android.com/apk/res/android</XML_NAMESPACE>\n                </AND>\n              </match>\n              <order>BY_NAME</order>\n            </rule>\n          </section>\n          <section>\n            <rule>\n              <match>\n                <AND>\n                  <NAME>.*</NAME>\n                  <XML_NAMESPACE>.*</XML_NAMESPACE>\n                </AND>\n              </match>\n              <order>BY_NAME</order>\n            </rule>\n          </section>\n        </rules>\n      </arrangement>\n    </codeStyleSettings>\n  </code_scheme>\n</component>"
  },
  {
    "path": ".idea/codeStyles/codeStyleConfig.xml",
    "content": "<component name=\"ProjectCodeStyleConfiguration\">\n  <state>\n    <option name=\"PREFERRED_PROJECT_CODE_STYLE\" value=\"Default (1)\" />\n  </state>\n</component>"
  },
  {
    "path": ".idea/compiler.xml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<project version=\"4\">\n  <component name=\"CompilerConfiguration\">\n    <option name=\"DEFAULT_COMPILER\" value=\"Javac\" />\n    <resourceExtensions />\n    <wildcardResourcePatterns>\n      <entry name=\"!?*.java\" />\n      <entry name=\"!?*.form\" />\n      <entry name=\"!?*.class\" />\n      <entry name=\"!?*.groovy\" />\n      <entry name=\"!?*.scala\" />\n      <entry name=\"!?*.flex\" />\n      <entry name=\"!?*.kt\" />\n      <entry name=\"!?*.clj\" />\n    </wildcardResourcePatterns>\n    <annotationProcessing>\n      <profile default=\"true\" name=\"Default\" enabled=\"false\">\n        <processorPath useClasspath=\"true\" />\n      </profile>\n    </annotationProcessing>\n  </component>\n</project>"
  },
  {
    "path": ".idea/copyright/apache2.xml",
    "content": "<component name=\"CopyrightManager\">\n  <copyright>\n    <option name=\"myName\" value=\"apache2\" />\n    <option name=\"notice\" value=\"Copyright (c) 2015 LingoChamp Inc.&#10;&#10;Licensed under the Apache License, Version 2.0 (the &quot;License&quot;);&#10;you may not use this file except in compliance with the License.&#10;You may obtain a copy of the License at&#10;&#10;  http://www.apache.org/licenses/LICENSE-2.0&#10;&#10;Unless required by applicable law or agreed to in writing, software&#10;distributed under the License is distributed on an &quot;AS IS&quot; BASIS,&#10;WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.&#10;See the License for the specific language governing permissions and&#10;limitations under the License.\" />\n  </copyright>\n</component>"
  },
  {
    "path": ".idea/copyright/profiles_settings.xml",
    "content": "<component name=\"CopyrightManager\">\n  <settings default=\"apache2\">\n    <LanguageOptions name=\"Groovy\">\n      <option name=\"fileTypeOverride\" value=\"1\" />\n    </LanguageOptions>\n    <LanguageOptions name=\"HTML\">\n      <option name=\"fileTypeOverride\" value=\"1\" />\n      <option name=\"prefixLines\" value=\"false\" />\n    </LanguageOptions>\n    <LanguageOptions name=\"Properties\">\n      <option name=\"fileTypeOverride\" value=\"1\" />\n    </LanguageOptions>\n    <LanguageOptions name=\"SPI\">\n      <option name=\"fileTypeOverride\" value=\"1\" />\n    </LanguageOptions>\n    <LanguageOptions name=\"XML\">\n      <option name=\"fileTypeOverride\" value=\"1\" />\n      <option name=\"prefixLines\" value=\"false\" />\n    </LanguageOptions>\n  </settings>\n</component>"
  },
  {
    "path": ".idea/dictionaries/Jacksgong.xml",
    "content": "<component name=\"ProjectDictionaryState\">\n  <dictionary name=\"Jacksgong\">\n    <words>\n      <w>buildship</w>\n      <w>chunked</w>\n      <w>classpathentry</w>\n      <w>dreamtobe</w>\n      <w>etag</w>\n      <w>filedownloader</w>\n      <w>gradleclasspathcontainer</w>\n      <w>gradleprojectbuilder</w>\n      <w>gradleprojectnature</w>\n      <w>jacksgong</w>\n      <w>javabuilder</w>\n      <w>javanature</w>\n      <w>liulishuo</w>\n      <w>okhttp</w>\n      <w>robolectric</w>\n      <w>sofar</w>\n    </words>\n  </dictionary>\n</component>"
  },
  {
    "path": ".idea/encodings.xml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<project version=\"4\">\n  <component name=\"Encoding\">\n    <file url=\"PROJECT\" charset=\"UTF-8\" />\n  </component>\n</project>"
  },
  {
    "path": ".idea/gradle.xml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<project version=\"4\">\n  <component name=\"GradleSettings\">\n    <option name=\"linkedExternalProjectsSettings\">\n      <GradleProjectSettings>\n        <compositeConfiguration>\n          <compositeBuild compositeDefinitionSource=\"SCRIPT\" />\n        </compositeConfiguration>\n        <option name=\"distributionType\" value=\"DEFAULT_WRAPPED\" />\n        <option name=\"externalProjectPath\" value=\"$PROJECT_DIR$\" />\n        <option name=\"modules\">\n          <set>\n            <option value=\"$PROJECT_DIR$\" />\n            <option value=\"$PROJECT_DIR$/demo\" />\n            <option value=\"$PROJECT_DIR$/library\" />\n          </set>\n        </option>\n        <option name=\"resolveModulePerSourceSet\" value=\"false\" />\n      </GradleProjectSettings>\n    </option>\n  </component>\n</project>"
  },
  {
    "path": ".idea/inspectionProfiles/Project_Default.xml",
    "content": "<component name=\"InspectionProjectProfileManager\">\n  <profile version=\"1.0\">\n    <option name=\"myName\" value=\"Project Default\" />\n    <inspection_tool class=\"AndroidLintMinSdkTooLow\" enabled=\"false\" level=\"WARNING\" enabled_by_default=\"false\" />\n    <inspection_tool class=\"AndroidLintMissingPermission\" enabled=\"false\" level=\"ERROR\" enabled_by_default=\"false\" />\n    <inspection_tool class=\"DanglingJavadoc\" enabled=\"false\" level=\"WARNING\" enabled_by_default=\"false\" />\n    <inspection_tool class=\"NullableProblems\" enabled=\"false\" level=\"WARNING\" enabled_by_default=\"false\">\n      <option name=\"REPORT_NULLABLE_METHOD_OVERRIDES_NOTNULL\" value=\"true\" />\n      <option name=\"REPORT_NOT_ANNOTATED_METHOD_OVERRIDES_NOTNULL\" value=\"true\" />\n      <option name=\"REPORT_NOTNULL_PARAMETER_OVERRIDES_NULLABLE\" value=\"true\" />\n      <option name=\"REPORT_NOT_ANNOTATED_PARAMETER_OVERRIDES_NOTNULL\" value=\"true\" />\n      <option name=\"REPORT_NOT_ANNOTATED_GETTER\" value=\"true\" />\n      <option name=\"REPORT_NOT_ANNOTATED_SETTER_PARAMETER\" value=\"true\" />\n      <option name=\"REPORT_ANNOTATION_NOT_PROPAGATED_TO_OVERRIDERS\" value=\"true\" />\n      <option name=\"REPORT_NULLS_PASSED_TO_NON_ANNOTATED_METHOD\" value=\"true\" />\n    </inspection_tool>\n    <inspection_tool class=\"SameParameterValue\" enabled=\"false\" level=\"WARNING\" enabled_by_default=\"false\" />\n    <inspection_tool class=\"SameReturnValue\" enabled=\"false\" level=\"WARNING\" enabled_by_default=\"false\" />\n    <inspection_tool class=\"UnusedReturnValue\" enabled=\"false\" level=\"WARNING\" enabled_by_default=\"false\" />\n    <inspection_tool class=\"WeakerAccess\" enabled=\"false\" level=\"WARNING\" enabled_by_default=\"false\">\n      <option name=\"SUGGEST_PACKAGE_LOCAL_FOR_MEMBERS\" value=\"true\" />\n      <option name=\"SUGGEST_PACKAGE_LOCAL_FOR_TOP_CLASSES\" value=\"true\" />\n      <option name=\"SUGGEST_PRIVATE_FOR_INNERS\" value=\"false\" />\n    </inspection_tool>\n    <inspection_tool class=\"unused\" enabled=\"false\" level=\"WARNING\" enabled_by_default=\"false\">\n      <option name=\"LOCAL_VARIABLE\" value=\"true\" />\n      <option name=\"FIELD\" value=\"true\" />\n      <option name=\"METHOD\" value=\"true\" />\n      <option name=\"CLASS\" value=\"true\" />\n      <option name=\"PARAMETER\" value=\"true\" />\n      <option name=\"REPORT_PARAMETER_FOR_PUBLIC_METHODS\" value=\"true\" />\n      <option name=\"ADD_MAINS_TO_ENTRIES\" value=\"true\" />\n      <option name=\"ADD_APPLET_TO_ENTRIES\" value=\"true\" />\n      <option name=\"ADD_SERVLET_TO_ENTRIES\" value=\"true\" />\n      <option name=\"ADD_NONJAVA_TO_ENTRIES\" value=\"true\" />\n    </inspection_tool>\n  </profile>\n</component>"
  },
  {
    "path": ".idea/inspectionProfiles/profiles_settings.xml",
    "content": "<component name=\"InspectionProjectProfileManager\">\n  <settings>\n    <option name=\"PROJECT_PROFILE\" value=\"Project Default\" />\n    <option name=\"USE_PROJECT_PROFILE\" value=\"true\" />\n    <version value=\"1.0\" />\n  </settings>\n</component>"
  },
  {
    "path": ".idea/modules.xml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<project version=\"4\">\n  <component name=\"ProjectModuleManager\">\n    <modules>\n      <module fileurl=\"file://$PROJECT_DIR$/FileDownloader.iml\" filepath=\"$PROJECT_DIR$/FileDownloader.iml\" />\n      <module fileurl=\"file://$PROJECT_DIR$/demo/demo.iml\" filepath=\"$PROJECT_DIR$/demo/demo.iml\" />\n      <module fileurl=\"file://$PROJECT_DIR$/library/library.iml\" filepath=\"$PROJECT_DIR$/library/library.iml\" />\n    </modules>\n  </component>\n</project>"
  },
  {
    "path": ".idea/runConfigurations.xml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<project version=\"4\">\n  <component name=\"RunConfigurationProducerService\">\n    <option name=\"ignoredProducers\">\n      <set>\n        <option value=\"org.jetbrains.plugins.gradle.execution.test.runner.AllInPackageGradleConfigurationProducer\" />\n        <option value=\"org.jetbrains.plugins.gradle.execution.test.runner.TestClassGradleConfigurationProducer\" />\n        <option value=\"org.jetbrains.plugins.gradle.execution.test.runner.TestMethodGradleConfigurationProducer\" />\n      </set>\n    </option>\n  </component>\n</project>"
  },
  {
    "path": ".idea/vcs.xml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<project version=\"4\">\n  <component name=\"VcsDirectoryMappings\">\n    <mapping directory=\"$PROJECT_DIR$\" vcs=\"Git\" />\n  </component>\n</project>"
  },
  {
    "path": ".travis.yml",
    "content": "language: android\n\njdk:\n# build-tools 24.0.2 need jdk8 or above.\n- oraclejdk8\nandroid:\n  components:\n    # Ref https://github.com/travis-ci/travis-ci/issues/6260.\n    - tools\n    - platform-tools\n    - build-tools-28.0.3\n    - android-28\n    - extra\n\nscript:\n  - ./gradlew clean check\n\nafter_script:\n    - cat ./demo/build/outputs/lint-results.xml\n    - cat ./library/build/outputs/lint-results.xml\n\nsudo: false\n\nbefore_cache:\n  - rm -f  $HOME/.gradle/caches/modules-2/modules-2.lock\n  - rm -fr $HOME/.gradle/caches/*/plugin-resolution/\n\ncache:\n  directories:\n    - $HOME/.gradle/caches/\n    - $HOME/.gradle/wrapper/\n    - $HOME/.android/build-cache\n"
  },
  {
    "path": "CHANGELOG-ZH.md",
    "content": "# Change log\n\n> [ Change log in english](https://github.com/lingochamp/FileDownloader/blob/master/CHANGELOG.md)\n\n## Version 1.7.7\n\n- 修复: FileDownloadThreadPool 可能会抛出 ArrayIndexOutOfBoundsException 和 ClassCastException。 closes #1258\n- 修复: 从 1.6.x 升级到 1.7.x 后恢复之前的下载任务时出现 416 错误。\n- 修复: Demo 中下载通知示例无法显示通知。closes #1224\n- 修复: blockComplete 可能会在主线程中回调。closes #1069\n- 修复: NoDatabaseImpl 中 SparseArray 非线程安全问题。closes #1225 \n\n## Version 1.7.6\n\n_2019-02-20_\n\n#### 修复\n\n- 修复: 在Android O以及更高版本手机上，在所有任务结束后自动将前台服务关闭. closes #1096\n- 修复: 修复'Context.startForegroundService() did not then call Service.startForeground()'的问题. closes #1104\n- 修复: 确保在调用停止任务后，运行中的通知被关闭. closes #1136\n- 修复: 修复在重试时小概率NPE. closes #1100\n\n## Version 1.7.5\n\n_2018-08-03_\n\n#### 修复\n\n- 修复: 修复在Android O的系统上，当应用不在前台，并且不在白名单的时候，由于下载服务无法通过`JobScheduler`来执行下载事务，只能通过`startService`，引起 \"Not allowed to start service Intent...\" 的问题。 closes #1078\n\n#### Enhance\n\n- 提升实用性: 支持`Content-Disposition`中的非UTF-8编码。 closes #1057\n- 提高实用性: 处理了阿里云服务错误反馈`416`的情况`。 closes #1050\n\n## Version 1.7.4\n\n_2018-05-19_\n\n#### 修复 \n\n- 修复: 修复在Android 8或更高版本上，当应用在后台时，并且此时正在下载，但是下载服务的链接断开，此时尝试重新绑定的时候发生'IllegalStateException'的问题。closes #1017\n- 修复: 修复响应头带回来的文件名可能存在安全隐患的问题. closes #1028\n\n## Version 1.7.3\n\n_2018-04-20_\n\n#### 修复\n\n修复: 修复由于在下载结束时`fd`没有被主动释放，导致当有大量的任务被不断的发起执行时有可能引发的OOM问题。\n\n## Version 1.7.2\n\n_2018-03-14_\n\n#### 修复\n\n- 修复: 将原本所需要下载的文件大小为`0`的时候，回调错误，修改为直接回调完成。closes #789\n- 修复: 修复当存在另外一个正在下载的相同临时文件路径的任务时，数据库中存在数据未被删除的问题。closes #953\n- 修复: 修复在重试后重试之前下载的进度丢失的问题。closes #949\n- 修复: 修复当试探连接没有提供`Content-Range`字段，但是提供`Content-Length`字段时，计算出的总长度始终是`1`的问题。\n\n#### 性能与提高\n\n- 提高实用性: 当在响应头中不存在`Content-Length`字段时，使用隐藏在`Content-Range`中的内容大小数据。 closes #967\n\n## Version 1.7.1\n\n_2018-02-05_\n\n#### 修复\n\n- 修复: 修复当后端不支持`HEAD`方法的时候，返回`405`响应状态导致下载失败的问题。 close #942\n\n## Version 1.7.0\n\n_2018-02-01_\n\n#### 修复\n\n- 修复: 通过同步处理暂停操作与状态的更新来修复状态不是一个正确向前的流的问题。 close #889\n- 修复: 修复在`pending`状态回调时带回来已经被弃用的`sofar-bytes`。 close #884\n- 修复: 修复当`filename`没有用`\"`包裹时，无法通过`content-dispostion`获取文件名的问题。 close #908\n- 修复: 修正`setCallbackProgressTimes`设置的次数不能正常生效的问题。 close #901\n- 修复: 修复由于试探连接采用`0-infinite`的`Range`导致下载了无用内容到tcp-window的问题。close #933\n- 修复: 在连接`ending`的时候再次主动关闭输入流，防止输入流泄漏特别是对于试探连接来说。\n\n#### 性能与提高\n\n- 提高实用性: 当临时文件重命名为目标文件成功时，不再做一次移除临时文件的操作，防止一些文件系统报错的问题。close #912\n- 提高实用性: 当确定本地提供的`Range`是正确的，但是后端却返回`416`时，将完全弃用`Range`请求头。close #921\n- 提高性能: 为试探连接使用`HEAD`的请求替代`GET`方法，提高试探通信效率。 ref #933\n\n#### 其他\n\n如果你正在使用`filedownloader-okhttp3-connection`，请将其更新到`1.1.0`版本来适配`1.7.0`版本。 \n\n## Version 1.6.9\n\n_2017-12-16_\n\n#### 修复\n\n- 修复(serial-queue): 修复在`FileDownloadSerialQueue`遇到的死锁。 closes #858\n- 修复: 不再在非单元测试环境使用`j-unit`，避免在一些小米手机上发生`no-static-method-found`的问题。 closes #867\n- 修复: 修复每次重试减少两次重试机会的问题。 closes #838\n- 修复: 修复在`pending`的时候暂停任务，而后获取到该任务都是`pending`的状态的问题。 closes #855\n\n#### 性能与提高\n\n- 提高实用性: 开放`SqliteDatabaseImpl`、`RemitDatabase`、`NoDatabaseImpl`，便于上层覆盖他们。\n- 提高实用性: 支持从更高的版本降级到该版本。\n- 提高实用性: 当上层没有主动添加`User-Agent`的时候，内部添加默认的`User-Agent`。 closes #848\n- 提高性能: 修改所有的线程池中线程的存活时间(从5s修改为15s)，避免在高频下载中，各池子频繁的释放与创建线程\n- 提高性能: 使用`RemitDatabase`作为默认的数据库，在很多小任务很快的结束下载(2s内)，其数据库操作将会变得十分冗余，而这部分的数据库操作将被取消\n\n#### RemitDatabase\n\nFileDownloader中大多数数据库长尾问题，是由于有很多很小的任务同时执行：\n\n- 由于很小的任务每次启动、等待、连接、下载进度、结束都会促发入库\n- 一旦任务很小网速很快的时候，一个小任务实际下载耗时可能在1-2s完成\n- 因此整个引擎不得不为该1-2s完成的任务完成一连串的数据库入库、更新到从数据库删除的操作\n- 也就是说单个类似的任务在1-2s内促发了至少5次数据库操作，期间包含入库与最后的删除\n- 一个任务还好，当这样的任务数上升到几百个的层面，这样高频持续的数据库操作，就很容易暴露各种数据库问题（包含文件系统问题）\n- 而现有体系在上层推任务大量任务到下载服务的时候， 会高频持续的3个并行对这些任务做入库处理，在这个点上数据库问题也容易发生（包含文件系统问题）\n\n---\n\n而相比之下写入数据库是为了断点续传，这个短期的频繁数据库操作，实质的作用甚微，早期的提供外接接口来控制下载进度间的入库频率显然无法覆盖该问题。\n\n---\n\n因此，还是为FileDownloader推出新的`RemitDatabase`用于解决该问题，除去期间的多线程安全问题的处理，核心思想如下:\n![][RemitDatabase-png]\n\n- 如果某一个任务的整体数据更新与结束在2s(该值可定制)内，则不再有数据库操作，全程只存内存\n- 如果某一个任务的数据更新与结束操过2s，则分为两部分，2s前只存内存，2s开始同时存内存与数据库\n- 如果某一个任务最终的结束是暂停或错误，则在最后的状态更新中，同时存内存与数据库\n\n## Version 1.6.8\n\n_2017-10-13_\n\n#### 修复\n\n- 修复: 修复断点续传失败, 由于Network线程中的`isAlive`不可靠导致的问题。 this closes #793\n- 修复: 修复断点续传失败，由于多个线程频繁的更新`status`并且`DownloadStatusCallback`的`sendMessage`无法保证有序性，导致下一次启动时最终状态是`process`无法断点续传(具体原因参看[这里](https://github.com/lingochamp/FileDownloader/issues/793#issuecomment-336370126))。 this refs #793, #764, #721, #769, #763, #761, #716\n- 修复: 不再由于任务已经结束依然存在需要派发的信息而让用户程序奔溃，因为这个对用户并不会照成影响。 this closes #562\n- 修复: 修复当用户频繁调用`pause`时，有可能出现`it can't take a snapshot for the task xxx`错误的问题。\n- 修复: 修复由于内部存储的任务对象大小存在问题，导致这样的对象任务每一次启动都必然会`416`的问题。\n\n## Version 1.6.7\n\n_2017-10-12_\n\n#### 修复\n\n- 修复: 避免`error`与`pause`的状态被运行中的状态覆盖导致下次断点续传失败。 closes #769, closes #764, closes #761, closes #763, closes #721, closes #716\n- 修复: 感谢 @hongbiangoal 对问题的定位，修复了当某一个分块的断点进度大于1.99G时，请求的范围出现负数的情况。 closes #791\n\n## Version 1.6.6\n\n_2017-09-29_\n\n#### 修复\n\n- 修复(文件完整性): 只有在确保缓存已经完全固化到本地文件了才更新数据库的进度，防止在该次暂停时固化失败，然后数据库更新了进度，导致下一次断点续传的时候(使用预分配策略的情况下)，本地已经下载的文件的进度与数据库记录的进度实际不一致，导致最后下载完成了文件不完整的问题。 Closes #745\n- 修复(清理): 修复调用`FileDownloader#clearAllTaskData`并没有清理连接表的问题。 Closes #754\n\n#### 性能与提高\n\n- 提高性能: 使用`BufferedOutputStream`来优化默认输出流，现在虚拟机内的缓存大小为8192字节。\n\n## Version 1.6.5\n\n_2017-09-11_\n\n#### 修复\n\n- 修复(crash): 修复因为使用`%d`格式化`AtomicLong`导致`IllegalFormatConversionException`的问题。 Closes #743\n\n## Version 1.6.4\n\n_2017-08-21_\n\n#### 新接口\n\n- 新增 `NoDatabaseImpl`: 为了有些用户需要一个没有数据库的FileDownloader的用户。 Refs #727\n\n#### 性能与提高\n\n- 提高性能: 使用`AtomicLong`代替锁的方式，使得下载进度递增性能更好。\n\n#### 修复\n\n- 修复(分块下载): 修复在断点续传时之前已经下载了分块下载的最后一块，可是在继续下载时重新请求了最后一块给了错误的Range导致416的错误。 Closes #737\n- 修复(npe): 修复极小概率当事件监听已经被其他线程移除时还在分发事件导致NPE的问题。 Closes #723\n\n## Version 1.6.3\n\n_2017-07-29_\n\n#### 修复\n\n- 修复: 修复当正在处理回调结束任务的事务时，调用了`pause`极小概率出现NPE的问题。 Closes #680\n- 修复: 修复当暂停或恢复`FileDownloaderSerialQueue`的时候，其已经完成了该操作，出现`MissingFormatArgumentException`的问题。 Closes #699\n\n## Version 1.6.2\n\n_2017-07-16_\n\n#### 修复\n\n- 修复: 修复当FileDownloader下载文件有一个分块从大于1.99G的地方开始下载，就会发生'offset < 0'异常的问题。 Closes #669\n\n## Version 1.6.1\n\n_2017-07-13_\n\n#### 性能与提高\n\n- 提高实用性: 当返回的`content-length`不等于通过Range计算出来的`content-length`时直接抛回`GiveUpException`而不继续下载。 Closes #636\n\n#### 修复\n\n- 修复: 修复下载出现错误或暂停下载时强制多`sync`了一次的问题。\n- 修复: 修复当从断点中恢复chuncked任务后下载文件被损坏的问题。\n\n## Version 1.6.0\n\n_2017-07-07_\n\n#### 修复\n\n- 修复(no-response): 修复当多线程分块下载同时完成时，有可能会由于线程安全问题导致completed无法得到回调的问题，具体情况参看[这里](https://github.com/lingochamp/FileDownloader/issues/631#issuecomment-313387299)。 Closes #631\n\n## Version 1.5.9\n\n_2017-07-04_\n\n#### 修复\n\n- 修复(duplicate-permission): 修复在Android 5.0或更高版本系统的手机中已经有一个应用引用了1.5.7或更新版本的FileDownloader后，再安装引用1.5.7或更新版本的FileDownloader的应用会报`INSTALL_FAILED_DUPLICATE_PERMISSION`的问题，这个问题是因为在1.5.7版本中我们申明了一个接收结束广播的权限导致，现在我们移除了这个权限申明来修复这个问题。Closes #641\n\n## Version 1.5.8\n\n_2017-06-28_\n\n#### 修复\n\n- 修复(无响应): 修复非常快速的对相同任务启动、暂停来回切换会使得任务到后面没有响应的问题。Closes #625\n\n## Version 1.5.7\n\n_2017-06-25_\n\n#### 新接口\n\n- 支持在`filedownloader.properties`中配置`broadcast.completed`: 决定是否需要在任务下载完成后发送一个完成的广播。 Closes #605\n- 支持接收201的http返回状态码。 Closes #545\n- 为`FileDownloadSerialQueue`支持暂停与继续功能. Closes #547\n- 在FileDownloader内部处理了各类重定向的情况(300、301、302、303、307、308)。 Closes #611\n- 弃用了`FileDownloader#init`取而代之的是`FileDownloader#setup`，现在如果你不需要定制组件，就只需要在使用FileDownloader之前的任意使用调用这个方法就行。 Closes #500\n\n> - 如果你使用`broadcast.completed`并且接收任务完成的广播,你需要在AndroidManifest中注册Action为`filedownloader.intent.action.completed`的广播并且使用`FileDownloadBroadcastHandler`来处理接收到的`Intent`。\n> - 现在, 不再使用`FileDownloader#init`, 取而代之的，如果你需要注册你的定制组件，你需要在`Application#onCreate`中调用`FileDownloader.setupOnApplicationOnCreate(application):InitCustomMaker`, 否则你只需要在使用FileDownloader之前的任意时候调用`FileDownloader.setup(Context)`即可。\n\n#### 修复\n\n- 修复: 修复来`FileDownloadQueueSet`无法处理使wifi-required失效的操作。 感谢 @qtstc\n- 修复(output-stream): 修复当提供的output-stream不支持seek时，FileDownloader无法使用的问题。 Closes #622\n\n#### 性能与提高\n\n- 提高实用性: 覆盖使用不同的Url来复用下载进度的情况（结合`idGenerator`一起使用)。 Closes #617\n\n## Version 1.5.6\n\n_2017-06-18_\n\n#### 修复\n\n- 修复(crash): 修复当调用`findRunningTaskIdBySameTempPath`的同时请求了暂停可能导致NPE奔溃的问题。 Closes #613\n- 修复(crash): 修复返回状态是`206`并且它的ETAG发生变化时导致`IllegalArgumentException`错误奔溃的问题。 Closes #612\n- 修复(crash): 修复当用户请求下载需要Wifi并当前不是Wifi环境时，出现`FileDownloadNetworkPolicyException`未处理导致奔溃的问题。 感谢 @qtstc\n- 修复(crash): 修复当用户直接从`v1.4.3`升级到`v1.5.2`并且在一些其他综合因素下（具体可以参见 #610 ) 初始化数据库时出现`IllegalStateException`错误奔溃的问题。Closes #610\n- 修复(crash): 修复当回调流已经结束当时与此同时刚好出现错误或下载完成或暂停，小概率会出现`IllegalStateException`奔溃的问题。\n- 修复(no-response): 修复在接收到`connected`回调之后，多线程下载建立连接，此时在检验连接与数据获取连接期间服务端数据发生错误或变更导致启动下载后没有响应的问题。\n\n#### 性能与提高\n\n- 提高实用性: 当父级目录创建失败时直接回调`error`。 Closes #542\n- 提高实用性: 处理了返回状态是`416`的情况。 Refs #612\n\n## Version 1.5.5\n\n_2017-06-12_\n\n#### 修复\n\n- 修复(max-network-thread-count): 修复当任务都是多线程下载时，`download.max-network-thread-count`参数没起作用，并同时下载任务无上限的问题。 Closes #607\n\n## Version 1.5.4\n\n_2017-06-11_\n\n#### 新接口\n\n- 通过`IdGenerator`支持了定制下载任务id生成器。 Closes #224\n\n#### 性能与提高\n\n- 提高实用性: 将`FileDownloadModel`的维护从`FileDownloadDatabase`中解藕，让`FileDownloadDatabase`只关心数据库相关操作。\n- 提高实用性: 将数据库初始化的维护工作从默认的数据库实现中解藕出来，让定制的数据库也能够被采用相同机制维护到。\n\n## Version 1.5.3\n\n_2017-06-08_\n\n#### 修复\n\n- 修复(crash): 修复在计算平均速度的过程中`connected`与`completed`几乎同时回调时发生`divide by zero`异常的问题。 Refs #601\n- 修复(crash): 修复触发暂停的同时，`FetchDataTask`已经被创建并请求执行，但是还没有来得及被执行，导致NPE奔溃的问题。 Closes #601\n\n## Version 1.5.2\n\n_2017-06-07_\n\n#### 修复\n\n- 修复(crash): 修复当任务需要回调`error`或者被暂停时，刚好该任务的某个或几个链接完成下载，此时遇到NPE或者是`ConcurrentModificationException`的异常。Closes #598\n- 修复(crash): 修复当任务被暂停时，任务从开始到被暂停还没来得及同步一次数据到文件系统或者数据库，此时遇到NPE的异常。Refs #598\n- 修复(crash): 修复当采用多链接下载一个任务时，非首次建链失败或者是创建`FetchDataTast`失败，此时遇到NPE的异常。Refs #598\n- 修复(speed-calculate): 修复忽略整个下载进度回调，并且只使用`FinishListener`时，此时下载速度始终是`0`的问题。\n- 修复(finish-listener): 修复对于之前已经下载好的任务，并且只监听来`FinishListener`，此时`FinishListener`的`over`方法不会被回调到的问题。\n\n#### 性能与提高\n\n- 提升性能: 开启了默认数据库的WAL，使得读与写可并行操作来提高性能，因为我们的绝大多数场景读写是会在不同线程中同时执行的，开启这个以后会导致内存的增加，但是在大多数情况下极大的提高了数据库的写入速度，并且更加稳定（更少的使用`fsync()`)。\n\n## Version 1.5.1\n\n_2017-06-05_\n\n#### 修复\n\n- 修复(crash): 修复在`FileDownloader.init`中，当没有提供`InitCustomMaker`时出现的NPE奔溃。 Closes #592\n- 修复(callback): 修复当之前有多个链接服务于该任务并且正在从端点恢复时，在`pending`中没有带回其正确的`sofarBytes`的问题。\n- 修复(speed-monitor): 矫正`IDownloadSpeed.Monitor`在断点续传下总平均速度不准确的问题。\n\n#### 性能与提高\n\n- 提高稳定性: 当触发暂停时，主动同步FetchTask中的进度确保其进度得到固化到文件系统。\n- 提高稳定性: 当在`FileDownloader.init`中提供的`context`为空时，抛`IllegalArgumentException`以更早的暴露问题。\n\n## Version 1.5.0\n\n_2017-06-05_\n\n#### 新接口\n\n- 支持对单个任务多连接(多线程)下载。  Closes #102\n- 支持通过`ConnectionCountAdapter`定制对每个任务使用连接(线程)数据的定制(可以通过`FileDownloader#init`设置进去)\n\n#### 性能与提高\n\n- 提高性能: 重构整个下载的逻辑与原始回调逻辑，并删除了1000行左右的`FileDownloadRunnable`。\n\n对于每个任务默认的连接(线程)数目策略，你可以通过`ConnectionCountAdapter`来定制自己的策略:\n\n- 1个连接(线程): 文件大小 [0, 1MB)\n- 2个连接(线程): 文件大小 [1MB, 5MB)\n- 3个连接(线程): 文件大小 [5MB, 50MB)\n- 4个连接(线程): 文件大小 [50MB, 100MB)\n- 5个连接(线程): 文件大小 [100MB, -)\n\n## Version 1.4.3\n\n_2017-05-07_\n\n#### 修复\n\n- 修复: 移除重复的被弃用的方法: `FileDownloader#init(Application)`, 因为`Application`是 `Context`的实现。\n\n## Version 1.4.2\n\n_2017-03-15_\n\n#### 修复\n\n- 修复(Same File Path): 避免多个问题同时对相同的文件写入，一旦存在另外一个正在运行中的任务与当前任务的文件存储路径一致，当前任务将会收到`PathConflictException`来拒绝启动。 Closes #471\n\n#### New Interfaces\n\n-  新增 `FileDownloadSerialQueue#getWaitingTaskCount`: 获取动态串行队列中正在等待启动的任务个数。Refs #345\n\n## Version 1.4.1\n\n_2017-02-03_\n\n#### 修复\n\n- 修复(高并发): 修复由于Messenger在已经收到结束的信息将Task对象赋值为Null以后依然收到其他消息，导致NPE的问题。 Closes #462\n- 修复(`FileDownloadHttpException`): 修复由于在建立连接后无法取到请求头以至于遇到`FileDownloadHttpException`时发生`IllegalStateException`的问题。 Closes #458\n\n## Version 1.4.0\n\n_2017-01-11_\n\n#### 性能与提高\n\n- 提高性能: 优化`FileDownloader#init`中的逻辑, 使其更加的轻量(仅仅做了赋值`context`与`maker`的操作)\n\n#### 修复\n\n- 修复(pause): 修复高并发情况下，当在启动一个任务的时候，很短的时间间隔内去暂停一个任务，可能无法暂停下来任务的问题。 Closes #402\n- 修复(init FileDownloader): 修复在很低概率下在`FileDownloadService`所在进程初始化FileDownloader时出现Crash的问题。 Closes #420  \n- 修复(FileDownloadHttpException): 修复在遇到`FileDownloadHttpException`类型Crash时，由于字符串的formatter无法匹配导致Crash的问题 Closes #438\n\n## Version 1.3.9\n\n_2016-12-18_\n\n### 核心:\n\n- 这个版本开始，你可以定制自己的网络连接组件: [FileDownloadConnection][FileDownloadConnection-java-link]，默认情况下我们使用[这个][FileDownloadUrlConnection-java-link]。\n- 这个版本开始，我们不再默认依赖okhttp，你可以根据自己的需求进行定制。(如果你依然想要使用okhttp，可以考虑集成下这个[仓库](https://github.com/Jacksgong/filedownloader-okhttp3-connection))\n\n> 如果你依然需要配置`timeout`、`proxy`，请不用担心，我已经对默认的网络连接组件实现了这几个接口: [DemoApplication](https://github.com/lingochamp/FileDownloader/blob/master/demo/src/main/java/com/liulishuo/filedownloader/demo/DemoApplication.java#L35)，如果有需要可以看看。\n\n#### 新接口\n\n- 新增 `FileDownloadQueueSet#reuseAndStart`: 添加 '复用并启动'接口，主要用于在启动队列任务之前，先对任务队列中的所有任务进行尽可能的复用。 Ref #383\n- 新增 `FileDownloadConnection`: 支持定制化网络连接组件，不再默认依赖okhttp。 Closes #158\n\n## Version 1.3.0\n\n_2016-10-31_\n\n#### 新接口\n\n- 新增 `FileDownloadSerialQueue`: 便于动态管理串行执行的队列。 Closes #345.\n- 移除 `FileDownloadListener`类中的`callback`方法, 并且新增`FileDownloadListener#isInvalid`方法，用于告知FileDownloader该监听器是否已经无效，不再接收任何消息。\n- 新增 `FileDownloader#clearAllTaskData`: 清空`filedownloader`数据库中的所有数据。 Closes #361.\n\n#### 性能与提高\n\n- 提高实用性(`FileDownloadListener#blackCompleted`): 确保`blockCompleted`回调可以接收任何的`Exception`。 Closes #369.\n- 提高实用性(service-not-connected): 在service-not-connected-helper中输出提示与原因, 这样当你调用有些需要确保下载服务已经连接上的方式，但下载服务没有连接上时，不但在`Logcat`中可以收到原因，还能收到提示。\n\n#### 修复\n\n- 修复(reuse): 修复调用`BaseDownloadTask#pause`之后短时间内调用`BaseDownloadTask#reuse`方法，可能会抛出异常的问题。 Closes #329.\n\n## Version 1.2.2\n\n_2016-10-15_\n\n#### 修复\n\n- 修复(fatal-crash): 修复当任务没有`FileDownloadListener`时，也不能收到该任务`FileDownloadMonitor.IMonitor#onTaskOver`的回调的问题。 Closes #348.\n\n## Version 1.2.1\n\n_2016-10-09_\n\n#### 修复\n\n- <s>修复(fatal-crash): 修复当任务没有`FileDownloadListener`时，也不能收到该任务`FileDownloadMonitor.IMonitor#onTaskOver`的回调的问题。 Closes #348.</s> 十分的抱歉这个问题在1.2.1版本中依然存在，最终在1.2.2中验证修复。\n\n## Version 1.2.0\n\n_2016-10-04_\n\n#### 新接口\n\n- 新增 `FileDownloader#insureServiceBind()`: 便于阻塞当前线程，并且启动下载服务，服务启动之后再执行需要服务的请求。 Refs #324.\n- 新增 `FileDownloader#insureServiceBindAsync()`: 便于启动下载服务，并且在服务启动之后，执行需要下载服务的请求。 Refs #324.\n- 新增 `FileDownloader#bindService(runnable:Runnable)`: 便于启动下载服务，并且在服务启动之后，执行 `runnable`。 Refs #324.\n- 新增 `FileDownloader#init(Context,InitCustomMaker)`: 便于初始化下载引擎的时候可以传入更多的定制化组件。 Refs #157.\n\n#### Enhancement\n\n- 提高实用性(`InitCustomMaker#database`): 支持定制化数据库组件(`FileDownloadDatabase`)，并且实现默认的数据库组件： `DefaultDatabaseImpl`。 Closes #157.\n- 提高实用性(`InitCustomMaker#outputStreamCreator`): 支持定制化输出流组件(`FileDownloadOutputStream`)，并且实现默认的输出流组件： `FileDownloadRandomAccessFile`，与一些可替代的组件： `FileDownloadBufferedOutputStream`、`FileDownloadOkio`。Closes #301.\n\n## Version 1.1.5\n\n_2016-09-29_\n\n#### 新接口\n\n- 支持在`filedownloader.properties`中配置`file.non-pre-allocation`: 是否不需要在开始下载的时候，预申请整个文件的大小(`content-length`), 默认值是`false`。Closes #313 .\n\n#### 修复\n\n- 修复(fatal-crash): 修复由于`ThreadPoolExecutor#getActiveCount()`是一个大概的值，导致在其反回的不是正确值时，thread-pool库中存在`StackOverflowError`Crash的问题。Closes #321 .\n- 修复(minor-crash): 修复在一些小概率情况下出现Crash Message是'No reused downloaded file in this message'的IllegalStateException的问题。 Closes #316 .\n- 修复(minor-crash): 修复当在下载服务还没有连接上时，同时有几个串行队列任务需要执行，在一些小概率的情况下，一些相同的任务会被启动两次导致crash的问题。 Refs #282 .\n\n#### 其他\n\n- 依赖: 取消对thread-pool库的依赖。 Refs #321 .\n- MinSDKVersion: 升级`minSdkVersion` : 8->9。 Refs #321 .\n\n## Version 1.1.0\n\n_2016-09-13_\n\n#### 新接口\n\n- 新增 `BaseDownloadTask#setWifiRequired`: 设置任务是否只允许在Wifi网络环境下进行下载。 默认值 `false`。 Closes #281 .\n\n#### 性能与提高\n\n- 提高性能: 替换所有的线程池为exceed-wait-pool(更多详情参见: `FileDownloadExecutors`) 并且所有线程池中的线程将会在闲置五秒后自动结束。 Refs #303 .\n- 提高实用性: 当有异常从`FileDownloadListener#blockComplete`抛出时，将会被`catch`并且回调到`FileDownloadListener#error`中而非回调`FileDownloadListener#completed`。 Closes #305 .\n\n#### 修复\n\n- 修复(lost-connect): 避免等待服务连接的列表中在一些小概率情况下存在重复任务的问题。\n\n## Version 1.0.2\n\n_2016-09-06_\n\n#### 修复\n\n- 修复: 服务还没有连接上时，启动的'队列任务'被放在等待队列，当服务连接上以后，FileDownloader尝试重启这些等待队列中的任务，但是抛了`IllegalStateException`的Bug。 Closes #307 .\n\n## Version 1.0.1\n\n_2016-09-05_\n\n#### 新接口\n\n> 如果你之前有使用现在已经被申明弃用的方法`BaseDownloadTask#ready()`, 只需要简单的将它迁移为:`BaseDownloadTask#asInQueueTask():InQueueTask`并且调用`InQueueTask#enqueue()`。\n\n- 添加`BaseDownloadTask#asInQueueTask():InQueueTask`并申明弃用`BaseDownloadTask#ready()`: 申明当前任务是队列任务，并且可以通过`InQueueTask#enqueue()`将当前任务放入全局队列以便于启动队列任务的时候，能被队列收编执行。`InQueueTask#enqueue()`中的操作与`BaseDownloadTask#ready()`相同, 我们通过这个方式封装`ready()`是为了让你更加清晰的了解: 只有当前任务是队列任务，才需要调用该方法；如果当前任务不是队列任务，而却调用了这个方法，你将会收到一个异常(具体异常的原因可以移步到`DownloadTask#start`报的异常信息进行了解)。\n\n#### 修复\n\n- 修复: 当有使用相同`listener`对象的多个孤立任务与队列任务在不同的线程中同时被启动时(后)，有可能会遇到IllegalStateException异常的问题。 Closes #282 .\n\n## Version 1.0.0\n\n_2016-08-21_\n\n#### 新接口\n\n- 添加 `BaseDownloadTask#cancel`: 这个方法是为了说明为什么`pause`的操作也可以达到`cancel`的作用。\n\n#### 性能与提高\n\n- 提高性能: 持有`isDownloaderProcess`的结果，防止多次判断。\n- 提高实用性: 重构代码的可见层。Closes #283\n- 提高实用性: 完善Java Doc。Closes #284\n- 提高实用性: 提供Java Doc 站点: http://fd.dreamtobe.cn 。Closes #284\n\n## Version 0.3.5\n\n_2016-08-16_\n\n#### 性能与提高\n\n- 提高实用性: 为FileDownloader中的所有线程添加线程名。\n- 提高性能: 调整`block-completed-thread-pool`中的核心线程数: 5->2，减少资源的浪费。\n\n#### 修复\n\n- 修复(SQLiteFullException): 覆盖了在整个下载过程中可能遇到`SQLiteFullException`的错误，就捕获相关错误并回调回 `FileDownloadListener#error` 。 Closes #243\n- 修复(提供目录的情况): 修复若是提供的是文件夹，并且对应的任务已经下载完成，再次启动的时候，在直接回调`FileDownloadListener#completed`时，获取的`targetFilePath`可能为null的问题。 Closes #237\n\n## Version 0.3.4\n\n_2016-07-31_\n\n#### 新接口\n\n- 添加 `FileDownloader#clear`: 用于强制根据任务ID清理其在filedownloader中的数据。Closes #218\n\n#### 性能与提高\n\n- 提高实用性: 为 `FileDownloader#start(FileDownloader, boolean)` 添加返回值: 是否成功启动任务下载。Closes #215\n- 提高实用性: `FileDownloader#pause` 暂停任务时，不再仅仅是暂停一个任务，而是暂停掉所有ID为指定ID的运行中的任务。\n\n#### 修复\n\n- 修复(初始化-CRASH): 修复初始化FileDownloader时，从`ActivityManager`获取到运行中进程信息为空时发生CRASH。Closes #210\n- 修复(小概率-CRASH): 修复当FileDownloadService已经`onDestroy`后，还接收到`snapshot-message`时发生CRASH的情况。 Closes #213\n- 修复(消息流准确性): 在真正启动下载时删除目标文件，以此保证当有相同任务正在下载时，获取下载状态，不会获取到已经下载完成的错误的状态。Closes #220\n- 修复(启动线性下载): 收集未绑定的任务进行启动而非只是根据FileDownloadListener去收集任务，修复无法启动两个相同`FileDownloadListener`的队列。Closes #233\n- 修复(清理Messenger): 在回调 结束的消息 的回调之前进行清理任务的Messenger，而非在回调之后清理，以此确保在回调方法中可以调用`BaseDownloadTask#reuse`。Closes #229\n\n#### 其他\n\n- 所依赖的okhttp从`3.3.1`升到`3.4.1`。\n\n## Version 0.3.3\n\n_2016-07-10_\n\n#### 新接口\n\n- 添加 `FileDownloadUtils#getTempPath`: 获取用于存储还未下载完成文件的临时存储路径: `filename.temp`。 Refs #172.\n- 添加 `FileDownloadUtils#isFilenameConverted(context:Context)`: 判断是否所有数据库中下载中的任务的文件名都已经从`filename`(在旧架构中)转为`filename.temp`。\n- 添加 `FileDownloader#getStatusIgnoreCompleted(id:int)`:  获取不包含已完成状态的下载状态(如果任务已经下载完成，将收到`INVALID`)。\n- 添加 `FileDownloader#getStatus(id:int, path:String)`:  获取下载状态。\n- 添加 `FileDownloader#getStatus(url:String, path:String)`:  获取下载状态\n- 添加 `FileDownloadUtils#generateId(url:String, path:String, pathAsDirectory:boolean)`: 生成可以被FileDownloader识别的`Download Id`。\n- 添加 `BaseDownloadTask#setPath(path:String, pathAsDirectory:boolean)`: 如果`pathAsDirectory`是`true`,`path`就是存储下载文件的文件目录(而不是路径)，此时默认情况下文件名`filename`将会默认从`response#header`中的`contentDisposition`中获得。\n- 添加 `BaseDownloadTask#isPathAsDirectory`: 判断`BaseDownloadTask#getPath()`返回的路径是文件存储目录(`directory`)，还是文件存储路径(`directory/filename`)。\n- 添加 `BaseDownloadTask#getTargetFilePath`: 获取目标文件的存储路径。\n- 添加 `FileDownloadQueueSet#setDirectory`: 设置队列中所有任务文件存储的目录。\n\n#### 性能与提高\n\n- 提高实用性: 支持将`path`作为目录来存储文件，在这个情况下，文件名默认将从`response#header`中的`contentDisposition`中获得。 Refs #200.\n- 提高实用性: 将还未下载完成的文件存储在临时文件中(`filename.temp`)。 Refs #172.\n- 提高性能: FileDownloader不再将已经完成下载的任务存储在数据库中，判定任务是否已经下载完成，直接通过判断目标文件是否存在。 Refs #176, #172.\n- 提高稳定性: 选用状态是`INVALID`或`progress`优先接收`completed`消息, 以此确保`connected`状态的任务能够留下来接收`progress`状态的消息。 Refs #123\n- 提高稳定性: 扩张 __任务同步锁__ 到 __获取相同ID任务队列__ 的外面，以此修复由于有些状态在 __获取相同ID任务队列__ 与 __等待任务同步锁__ 的过程中已经被改变导致有些消息不能被消耗的问题。\n\n#### 修复\n\n- 修复(DB-维护): 保留状态是`pending`并且已经下载的字节数大于0的Model，因为这些Model可以用于恢复断点续传。 Closes #176.\n- 修复(crash-NPE): FileDownloader 可能遇到NPE当下载监听器被移除，但是对应任务还在FileDownloader中运行。 Closes #171.\n\n## Version 0.3.2\n\n_2016-06-12_\n\n#### 新接口\n\n- 添加 `BaseDownloadTask#setCallbackProgressMinInterval`: 用于设置每个'progress'方法回调的间隔。 Closes #167 .\n- 添加 `FileDownloader#setMaxNetworkThreadCount`: 用于设置最大同时下载的数目（最大同时运行的网络线程）。 Closes #168.\n- 添加 `FileDownloader#init(Context,OkHttpClientCustomMaker,int)`: 在下载服务初始化的时候接受设置最大同时下载数目（最大同时运行的网络线程）。 Closes #168.\n\n#### 性能与提高\n\n- 提高稳定性: 确保每个'progress'回调方法之间的最小间隔是5ms，防止对于一个任务而言'progress'回调太频繁导致'防掉帧队列'极速膨胀导致各类Action响应都延时。 Closes #167.\n- 提高实用性: 在请求的操作需要在下载服务中完成，但是还未连接上下载服务时，输出对应的'warn'级别的日志。\n- 提高性能: 使用`SparseArray`代替`HashMap`用于索引所有的`FileDownloadModel`。\n\n#### 修复\n\n- 修复(crash): 修复在某个下载任务开始下载时，发现任务的状态不正确的情况下，输出日志中提供了错误的参数类型导致的Crash。\n- 修复(强制重新下载): 修复错误逻辑导致设置`BaseDownloadTask#setForceReDownload(true)`并且任务已经下载完成会促发'warn'的回调，却没有进行强制重新下载的Bug。\n- 修复(class-type): 保持`SocketTimeOutException`的Class类型，不再关心`Throwable`的`message`是否为空。\n\n#### 其他\n\n- 所依赖的okhttp从`3.2.0`升到`3.3.1`。\n\n## Version 0.3.1\n\n_2016-05-19_\n\n#### 性能与提高\n\n- 提高稳定性: 在结束下载时确保缓存中的数据都写入文件。\n\n## Version 0.3.0\n\n_2016-05-13_\n\n#### 修复\n\n> 为什么FileDownload服务可以运行在UI进程? 参考 [filedownloader.properties](https://github.com/lingochamp/FileDownloader/wiki/filedownloader.properties).\n\n- 修复(下载服务共享UI进程时): 修复在下载服务不是运行在独立进程的情况下（非默认情况），附加的header没有带上请求的bug。Closes #149.\n\n## Version 0.2.9\n\n_2016-05-10_\n\n#### 新接口\n\n- 添加 `BaseDownloadTask#isUsing():boolean`: 用于判断当前的Task对象是否在引擎中启动过. Closes #137 。\n\n#### 修复\n\n- 修复(高并发情况下的npe): 当任务的状态是一个未预期的状态是，提供一个默认的错误快照，避免出现npe 。\n- 修复(返回错误码-416): 覆盖返回错误码是416或者当出现已下载大小大于等于文件总大小的时候依然断点续传的bug。\n\n## Version 0.2.8\n\n_2016-05-02_\n\n#### 新接口\n\n- 添加 `BaseDownloadTask#getId():int`: 弃用(没有删除该接口) `getDownloadId()`, 建议使用 `getId()` 代替 。\n\n#### 性能与提高\n\n- 提高稳定性: 重构任务启动器，使得启动任务更加可维护，以及标记任务过期更加可靠。\n- 提高稳定性: 重构将事件派发给`FileDownloadListener`的体系，新的体系就如同，派件员与快递驿站的关系，每次都会对事件进行快照，打包为一个消息快件，派发到驿站，转包给 `FileDownloadListener`。\n- 提高稳定性: 覆盖所有的有关暂停的高并发情况，删掉一些符合预期的警告性日志。\n- 提高性能: 减少FileDownloader database I/O 。\n- 提高性能: 减少创建对象(更少的内存分配请求，对于GC友好)对于每次回调, 对于一个下载状态的更新，只创建一个快照，整个通讯架构使用。\n\n#### 修复\n\n- 修复: 提供明确的locale用于格式化字符串，避免一些默认locale是非预期的情况发生。Closes #127\n\n## Version 0.2.7\n\n_2016-04-22_\n\n#### 新接口\n\n- 添加 `FileDownloader#setTaskCompleted(taskAtomList:List<FileDownloadTaskAtom>)`: 用于告诉FileDownloader引擎，指定的一系列的任务都已经通过其他方式(非FileDownloader)下载完成。\n\n#### 性能与提高\n\n- 提高稳定性: 假如在下载进程调用 `bindService` 直接抛异常，防止用户在使用过程中，错误的在下载进程绑定服务，而没有暴露这个根本问题，引发其他一系列的异常。Closes #119。\n\n## Version 0.2.6\n\n_2016-04-20_\n\n#### 新接口\n\n- 调整: 将原本需要在根目录创建的 `filedownloader.properties` ，改为到 模块的 `assets` 目录下， 如 `/demo/src/main/assets/filedownloader.properties`。\n\n#### 修复\n\n- 修复 `filedownloader.properties` 中的参数不起作用的bug。 Closes #117.\n\n## Version 0.2.5\n\n_2016-04-19_\n\n#### 新接口\n\n- 添加 `FileDownloader#setTaskCompleted`: 用于告诉FileDownloader引擎，以指定Url与Path的任务已经通过其他方式(非FileDownloader)下载完成。\n- 支持 新的配置参数`download.max-network-thread-count` 在 `filedownloader.properties`: 同时下载的最大网络线程数，默认值是3。 Closes #116.\n\n## Version 0.2.4\n\n_2016-04-18_\n\n#### 新接口\n\n- 添加 `BaseDownloadTask#getSpeed` 以及 `BaseDownloadTask#setMinIntervalUpdateSpeed`: 获取任务的下载速度, 下载过程中为实时速度，下载结束状态为平均速度。 Closes #95 。\n- 添加 `FileDownloader#startForeground` 以及 `FileDownloader#stopForeground` 用于支持 前台模式([Service#startForeground](http://developer.android.com/intl/zh-cn/reference/android/app/Service.html#startForeground(int, android.app.Notification)))，保证用户从最近应用列表移除应用以后下载服务被杀。 Closes #110 。\n- 支持 新的配置参数 `download.min-progress-step` 以及 `download.min-progress-time`: 最小缓冲大小以及最小缓冲时间，用于判定是否是时候将缓冲区中进度同步到数据库，以及是否是时候要确保下缓存区的数据都已经写文件。这两个值越小，更新会越频繁，下载速度会越慢，但是应对进程被无法预料的情况杀死时会更加安全。默认值是与 `com.android.providers.downloads.Constants`中的一致 65536(最小缓冲大小) 以及 2000(最小缓冲时间)。\n- 支持 新的配置参数 `process.non-separate` 在 `filedownloader.properties` 中 : FileDownloadService 默认是运行在独立进程 `:filedownloader` 上的, 如果你想要FileDownloadService共享并运行在主进程上，以减少不必要的消耗(如IPC的I/O，维护进程的CPU的消耗等), 添加将该配置参数值设置为 `true`。 Closes #106 。\n\n#### 性能与提高\n\n- 提高性能: 提高了下载速度, 优化了同步缓冲区的数据到本地文件以及数据库的架构，很大程度的提高了下载速度。 Closes #112 。\n\n#### 修复\n\n- 修复: 无法重新启动一个已经暂停但是依然存在下载线程池中在pending中的任务。 Closes #111 。\n\n## Version 0.2.3\n\n_2016-04-11_\n\n#### 新接口\n\n- 添加 `FileDownloadOutOfSpaceException`, 当将要下载的文件大小大于剩余磁盘大小时，会抛出这个异常。\n- 在 `FileDownloadListener` 新增 `started` 回调方法: 在结束 `pending` 开始运行当前任务的线程时，回调该方法。\n- 在 `FileDownloadMonitor.IMonitor` 新增 `onTaskStarted` 回调方法，用于监控在结束 `pending` 开始运行当前任务的线程时，回调该方法。这样就可以在监控中通过 `onTaskBegin`到`onTaskStarted`计算出pending的时间，在`onTaskStarted`到`onTaskOver`计算出真正消耗在下载的时间(Connection、Fetching)。\n\n#### 性能与提高\n\n- 提高实用性: 为 `FinishListener` 的 `over` 方法提供所指向的Task，为了有些时候我们为多个任务添加相同的 `FinishListener` 时，需要这个参数来判断当前是哪个任务的回调。 Closes #69 。\n- 提高稳定性: 如果调用一个正在运行中的Task对象的 `start` 方法，直接抛异常；并且为已经结束的Task对象提供 `BaseDownloadTask#reuse` 进行复用。 Closes #91 。\n- 提高性能: 在进入事件队列之前，拦截掉一些原本就没有监听器进行监听的事件。\n\n#### 修复\n\n- 修复: 在一些极端情况下，非结束的回调回调次数不符合预期的情况。\n- 修复: `progress` 方法的回调中包含了对完成( `sofarBytes==totalBytes` )的回调，导致回调间隔不达预期的bug。\n- 修复: 在 `warn` 回调带回 total-bytes，为了覆盖在 主进程被杀，下载进程存在的情况下，主进程重新重启并启动相同任务，total-bytes为0的bug。 Closes #90 。\n- 修复: 如果连续出现失败，连续回调 `retry` 时，`retry` 只被回调一次，其他的次数的 `retry` 都不被回调的bug。 Refs: #91 。\n- 修复: 在无网络状态下，启动下载，如果存在重试的机会，下载的进度被覆盖，导致下次无法断点续传的bug。 Closes #92 。\n- 修复: 有可能在'检测是否可以复用'到'检测是否在下载队列'的这段时间内已经下载完成但是任务还在队列中的极端情况。\n- 修复: 线性任务，在下载进程被杀重新启动被转为并行任务的bug。\n\n## Version 0.2.2\n\n_2016-04-06_\n\n#### 新接口\n\n- 添加 `FileDownloadHttpException` 与 `FileDownloadGiveUpRetryException`, 优化异常回调处理机制. Closes #67 。\n- 初始化 `FileDownloader` 传入参数由原来需要 `Application` 改为 需要 `Context`( `FileDownloader#init(Context)` ), 优化接口，并且便于单元测试。 Closes #54 。\n\n#### 性能与提高\n\n- 提高稳定性: 在开始获取数据之前，先检查是否有足够的空间用于存储下载文件，如果不够直接抛异常，如果足够将锁定对应空间用于正常存储正在下载的文件。 Closes #46 。\n- 提高实用性: 断点续传支持，不再强制要求Etag存在；支持不需要Etag，只要后台支持 `Range` 头部参数就可以支持断点续传。 Close #35 , #66 。\n\n#### 修复\n\n- 修复: 在 `FileDownloadLog.NEED_LOG` 为 `true` 时，并且事件无效的情况下，`EventPool` 出现 `IllegalFormatConversionException` 异常的问题。 Closes #30 。\n- 修复: 在 Filedownloader进程被杀以后， 在 `IFileDownloadIPCService` 出现异常。Closes #38 。\n- 修复: 修复 response-body 可能存在的泄漏: 'WARNING: A connection to https://... was leaked. Did you forget to close a response body?' Closes #68 。\n- 修复: 使用 `internal-string` 作为同步的对象，而非直接用 String对象。\n- 修复: 在一些情况下如果存在重复任务，在高并发下进行中的回调次数可能不对的bug。\n\n#### 其他\n\n- 所依赖的okhttp从`3.1.2`升到`3.2.0`。\n\n## Version 0.2.0\n\n_2016-02-15_\n\n#### 新接口\n\n- `filedownloader.properties-http.lenient`: 添加`http.lenient`用于配置下载引擎中是否需要忽略一些http规范性的错误(如: 忽略 `can't know the size of the download file, and its Transfer-Encoding is not Chunked`), 默认值`false`。\n- `FileDownloadNotificationHelper`: 用于支持在通知栏中的通知对下载引擎中任务下载状态同步的快速集成。\n- `FileDownloader#init(Application,OkHttpClientCustomMaker)`: 用于为下载引擎提供定制的OkHttpClient。\n\n#### 修复\n\n- 修复: 需要重新启动的列表(`FileDownloadTask.NEED_RESTART_LIST`)不为空并且下载服务断开时出现`Concurrent Modification Exception`的异常。\n- 修复: 下载引擎连接丢失以后，重连任务的回调失效的bug。\n- 修复: 在一些高并发下载情况下，对队列进行暂停，部分暂停不生效的bug。\n\n## Version 0.1.9\n\n_2016-01-23_\n\n> 引擎默认会打开 避免掉帧的处理(使得一次回调(FileDownloadListener)不至于太频繁导致手机显示掉帧), 如果你希望关闭这个功能（关闭以后，所有回调会与之前版本一样，所有的回调会立马抛一个消息ui线程(Handler)）: `FileDownloader.getImpl().disableAvoidDropFrame()`.\n\n#### 新接口\n\n\n- `FileDownloadMonitor`: 现在你可以通过这个来添加一个全局的监听器，方便调试或打点\n- `FileDownloader#enableAvoidDropFrame(void)`: 开启 避免掉帧, 原理最多10ms抛一个消息到ui线程，每次在ui线程每次处理5个回调(FileDownloadListener), 默认: 开启。\n- `FileDownloader#disableAvoidDropFrame(void)`: 关闭 避免掉帧，会和之前的版本一样，每个回调(FileDownloadListener)都抛一个消息到ui线程，如果频率非常高（如高并发的文件检测）可以导致ui线程被DDOS。\n- `FileDownloader#isEnabledAvoidDropFrame(void)`: 是否是 开启了避免掉帧，目前如果没有设置默认是开启的。\n- `FileDownloader#setGlobalPost2UIInterval(intervalMillisecond:int)`: 设置最多intervalMillisecond毫秒抛一个消息到ui线程，是 避免掉帧的具体设置。默认: 10ms，如果设置为小于0的数值，会 关闭 避免掉帧。\n- `FileDownloader#setGlobalHandleSubPackageSize(packageSize:int)`: 设置每次在ui线程每次处理packageSize个回调，如果已经关闭了 避免掉帧，那么这个值将没有任何意义，默认: 5个。\n- `BaseDownloadTask#setSyncCallback(syncCallback:boolean)`: 是否同步回调该task中的所有的回调(FileDownloadListener), 如果设为true, 该task的所有回调会直接在下载线程直接回调，不会抛到ui线程, 默认: false。\n- `BaseDownloadTask#isSyncCallback(void):boolean`: 该task是否设置了所有回调(FileDownloadListener)同步调用(直接在下载线程直接调用，而非抛到ui线程)。\n- `FileDownloadUtils#setDefaultSaveRootPath`: 设置全局默认的存储路径(Root Path)，在task没有指定对应的存储路径的时候，会存储在该目录下。\n- `FileDownloadQueueSet`: 用于更方便的指定几个task为一个队列，进行并行/串行下载，并且可以很方便的对整个队列中的所有任务进行统一设置。\n\n#### 性能与提高\n\n- 提高可调试性: 提供了一个全局监听器(`FileDownloadMonitor`)，更方便与调试或打点。\n- 提高性能: 优化内部EventPool的锁机制，不再处理listener的priority。\n- 提高性能: 所有`FileDownloadListener`中的回调将会直接调用，而不再过一层EventPool。\n\n#### 修复\n\n- 修复: `EventPool`中的listener存储器无限制的bug.\n\n## Version 0.1.5\n\n_2016-01-17_\n\n#### 新接口\n\n- `BaseDownloadTask#setTag(key:int, tag:Object)`: 用于存储任意的变量方便回调中使用，以key作为索引。\n- `BaseDownloadTask#getTag(key:int)`: 根据key获取存储在task中的变量。\n- `BaseDownloadTask#addHeader(name:String, value:String)`: 添加自定义的请求头参数，需要注意的是内部为了断点续传，在判断断点续传有效时会自动添加上(`If-Match`与`Range`参数)，请勿重复添加导致400或其他错误。\n- `BaseDownloadTask#addHeader(line:String)`: 添加自定义的请求头参数，需要注意的是内部为了断点续传，在判断断点续传有效时会自动添加上(`If-Match`与`Range`参数)，请勿重复添加导致400或其他错误。\n- `BaseDownloadTask#removeAllHeaders(name:String)`: 删除由自定义添加上去请求参数为`{name}`的所有键对。\n\n#### 性能与提高\n\n- 提高性能: 在未打开Log的情况下，屏蔽了所有Log生成的代码。\n- 提高可调试性: 重新过滤所有的日志级别，减少高级别日志输出，并且默认将会打出`Warn`、`Error`、`Assert`级别的log以便于用户在未打开日志的情况下也可以定位到基本的组件异常。\n\n#### 修复\n\n- 修复: 在一些高并发的情况下，有可能内部队列存在残留任务的bug，此bug可能可能引发回调被旧的任务吞掉的问题。\n- 修复: 出现网络错误，或者其他错误，重新下载无法自动断点续传的bug。\n\n#### 其他\n\n- 所依赖的okhttp从`2.7.1`升到`3.0.1`。\n\n## Version 0.1.4\n\n_2016-01-13_\n\n#### 新接口\n\n- `FileDownloader#unBindServiceIfIdle(void)`: 如果目前下载进程没有任务正在执行，则关停下载进程\n- `FileDownloader#getStatus(downloadId)`: 获取下载Id为downloadId的状态(可参考[任务管理demo](https://github.com/lingochamp/FileDownloader/blob/master/demo/src/main/java/com/liulishuo/filedownloader/demo/TasksManagerDemoActivity.java))\n- `FileDownloader#isServiceConnected(void)`: 是否已经启动并且连接上下载进程(可参考[任务管理demo](https://github.com/lingochamp/FileDownloader/blob/master/demo/src/main/java/com/liulishuo/filedownloader/demo/TasksManagerDemoActivity.java))\n\n#### 性能与提高\n\n- 支持[Chunked transfer encoding](https://en.wikipedia.org/wiki/Chunked_transfer_encoding) 数据下载(建议看一眼[Single Task Test](https://github.com/lingochamp/FileDownloader/blob/master/demo/src/main/java/com/liulishuo/filedownloader/demo/SingleTaskTestActivity.java)).\n- 提高性能: 减少 IPC。\n- 提高性能: 减少线程锁。\n- 提高性能: 在`:filedownloader`进程启动时，对数据库中的数据进行第一级别维护。\n- 提高性能: 忽略数据库中的`callbackProgressTimes`字段。\n\n#### 修复\n\n- 修复: 在低内存情况下，ui进程处于后台进程的情况下被回收，而下载进程(服务进程)还在, 并且还存在在下载中的任务，此时重新启动ui进程`FileDownloader#pauseAll`无法暂停已经在下载进程启动的任务的bug。\n- 修复: 主动调用`FileDownloader#unBinderService`，没有释放连接相关资源的bug。\n- 修复: ui进程被干掉，下载进程健还有活跃的并行任务正在下载，ui进程启动以后启动相同的队列列表，无法收到进度只收到warn的bug。\n\n## Version 0.1.3\n\n_2016-01-04_\n\n- 不再受到1.99G限制;如果是大于1.99G的文件，请使用`FileDownloadLargeFileListener`作为监听器，使用对应的`getLargeFileSoFarBytes()`与`getLargeFileTotalBytes()`接口\n- 性能优化: 部分接口跨进程通信不受binder thread 阻塞。\n- 依赖okhttp，从`2.7.0`升到`2.7.1`\n\n## Version 0.1.2\n\n_2015-12-27_\n\n- 优化线程消化能力\n- 修复大队列任务暂停可能部分无效的问题\n- 修复大队列并行下载时一定概率下载已经完成回调囤积延后回调的问题\n\n## Version 0.1.1\n\n_2015-12-25_\n\n- event线程区分敏捷线程池与其他线程池，减少资源冗余强制、内部稳定性以及消化能力与性能，\n- 添加自动重试接口，新增用户指定如果失败自动重试的次数\n\n## Version 0.1.0\n\n_2015-12-24_\n\n- FileDownloadStatus 由`int`改为`byte`，该参数会频繁的在IPC时被拷贝\n- 优化串行or并行任务时，筛选task在准备数据时就筛选好，减少冗余操作，更加安全\n- 优化串行任务执行保证使用更可靠的方式\n\n## Version 0.0.9\n\n_2015-12-23_\n\n- 将调用start(启动任务)抛独立线程处理，其中的线程是通过共享非下载进程EventPool中的线程池(可并行8个线程)\n\n## Version 0.0.8\n\n_2015-12-22_\n\n- initial release\n\n[RemitDatabase-png]: https://github.com/lingochamp/FileDownloader/raw/master/art/remit-database.png\n[FileDownloadConnection-java-link]: https://github.com/lingochamp/FileDownloader/blob/master/library/src/main/java/com/liulishuo/filedownloader/connection/FileDownloadConnection.java\n[FileDownloadUrlConnection-java-link]: https://github.com/lingochamp/FileDownloader/blob/master/library/src/main/java/com/liulishuo/filedownloader/connection/FileDownloadUrlConnection.java\n"
  },
  {
    "path": "CHANGELOG.md",
    "content": "# Change log\n\n> [中文迭代日志](https://github.com/lingochamp/FileDownloader/blob/master/CHANGELOG-ZH.md)\n\n## Version 1.7.7\n\n- Fix: FileDownloadThreadPool may throw ArrayIndexOutOfBoundsException & ClassCastException. closes #1258\n- Fix: Resume a task may occur 416 problem after upgrading from 1.6.x to 1.7.x.\n- Fix: Cannot show notification in demo. closes #1224\n- Fix: The callback blockComplete may be invoked in main thread.closes #1069\n- Fix: The thread unsafe problem of SparseArray in NoDatabaseImpl. closes #1225 \n\n## Version 1.7.6\n\n_2019-02-20_\n\n#### Fix\n\n- Fix: stop foreground service after all tasks finished in Android O. closes #1096\n- Fix: fix 'Context.startForegroundService() did not then call Service.startForeground()' issue. closes #1104\n- Fix: insure all foreground service running notification is canceled when pause download. closes #1136\n- Fix: fix tiny possibility npe during retry. closes #1100\n\n## Version 1.7.5\n\n_2018-08-03_\n\n#### Fix\n\n- Fix: fix raise \"Not allowed to start service Intent...\" issue when starting DownloadService on Android O and the application isn't on the foreground and also not on the whitelist, because we can't use `JobScheduler` to handle the download affair. closes #1078\n\n#### Enhance\n\n- Improve Practicability: support character set and the launguage encoding for `Content-Disposition`. closes #1057\n- Improve Practicability: cover the error response code 416 from aliyun repo. closes #1050\n\n## Version 1.7.4\n\n_2018-05-19_\n\n#### Fix\n\n- Fix: fix raise 'IllegalStateException' on Android 8+ when FileDownloader try to re-bind service after the connection with the service is lost on downloading state and the app is on the background. closes #1017\n- Fix: fix directory traversal vulnerability security issue. closes #1028\n\n## Version 1.7.3\n\n_2018-04-20_\n\n#### Fix\n\nFix: fix `fd` isn't released manually when download finished which may raise oom when there are a large number of tasks that are continuously initiated.\n\n## Version 1.7.2\n\n_2018-03-14_\n\n#### Fix\n\n- Fix: do not download callback error when the instance length of the task is zero, callback complete directly instead. closes #789\n- Fix: fix the temp duplicate data in the database isn't removed when there is another running task with the same temp file path. closes #953\n- Fix: the data lost when retry. closes #949\n- Fix: fix the instance-length is always 1 when the Content-Range isn't provided but the Content-Length is provided on the trial connection.\n\n#### Enhancement\n\n- Improve Practicability: using the content length value on the Content-Range field when there isn't Content-Length field found in the response header. closes #967\n\n## Version 1.7.1\n\n_2018-02-05_\n\n#### Fix\n\n- Fix: fix download failed with 405 response code when backend can't support `HEAD` method. close #942\n\n## Version 1.7.0\n\n_2018-02-01_\n\n#### Fix\n\n- Fix: fix update status can't keep flow through making updating status synchronized with pause action. close #889\n- Fix: fix the sofar-bytes carry back through pending state callback has already discarded. close #884\n- Fix: fix can't find filename if filename value on content-disposition without around with \". close #908\n- Fix: correct `setCallbackProgressTimes` method make `setCallbackProgressTimes` work correctly. close #901\n- Fix: fix download useless data on tcp-window because of the first trial connection use `0-infinite` range. close #933\n- Fix: close intput stream when connection ending avoid input-stream leak especially for the trial connection.\n\n#### Enhancement\n\n- Improve Practicability: do not remove the temp-file if rename it to the target path success to prevent raise some file-system problem. close #912\n- Improve Practicability: discard range totally if range is right but backend response 416. close #921\n- Improve Performance: using HEAD request method instead of GET method for trial connect. ref #933\n\n#### Other\n\nIf you are using filedownloader-okhttp3-connection, please upgrade it to the `1.1.0` to adapter FileDownloader 1.7.0.\n\n## Version 1.6.9\n\n_2017-12-16_\n\n#### Fix\n\n- Fix(serial-queue): fix deadlock on `FileDownloadSerialQueue`. closes #858\n- Fix: do not use j-unit on library non-unit-test env to fix the `no-static-method-found` issue on some mi-phones. closes #867\n- Fix: fix decrease two times of retry-chance each time of retry. closes #838\n- Fix: fix get status is pending when a task has been paused on pending status. closes #855\n\n#### Enhancement\n\n- Improve Practicability: public `SqliteDatabaseImpl`、`RemitDatabase`、`NoDatabaseImpl`, so you can overwrite them\n- Improve Practicability: support downgrade version from newer version\n- Improve Practicability: add the default `User-Agent` if upper layer does not add. closes #848\n- Improve Performance: change the keepalive second(5s to 15s) for each executor, since when downloading multiple tasks thread release and recreate too frequently\n- Improve Performance: using `RemitDatabase` instead of `DefaultFiledownloadDatabase` to avoid some small task start and finished on the very short time but consume too much time on sync status to database what is useless\n\n![][RemitDatabase-png]\n\n## Version 1.6.8\n\n_2017-10-13_\n\n#### Fix\n\n- Fix: fix resume from breakpoint failed because of `isAlive` not guarantee on Network-thread. this closes #793\n- Fix: fix resume from breakpoint failed, because of multi-thread update status very frequently and Messenger can't guarantee order. this refs #793, #764, #721, #769, #763, #761, #716\n- Fix: do not crash user when a task has finished but the messenger still has messages, because it's fine for the user. this closes #562\n- Fix: fix the callback error of 'it can't take a snapshot for the task xxx' when a user invokes pause very frequently.\n- Fix: fix the case of process on the model is wrong which raise 416 each time when restarting it.\n\n## Version 1.6.7\n\n_2017-10-12_\n\n#### Fix\n\n- Fix: Avoid error/pause status is covered by other processing-status which will cause resume-failed, task-never-end. this closes #769, closes #764, closes #761, closes #763, closes #721, closes #716\n- Fix: Fix request range value turn to negative when resuming a task which has a process more than 1.99G on its one block. Thanks to @hongbiangoal closes #791\n\n## Version 1.6.6\n\n_2017-09-29_\n\n#### Fix\n\n- Fix(file-integrality): update the process to database only if all buffers on output-stream or system has been flush and sync to device successfully to avoid resume on the wrong point raise complete file not integrality. Closes #745\n- Fix(clear): fix `FileDownloader#clearAllTaskData` not clear connection table. Closes #754\n\n#### Enhancement\n\n- Import Performance: optimize the default output-stream with buffered-output-stream, now the VM buffers length is 8192 bytes.\n\n## Version 1.6.5\n\n_2017-09-11_\n\n#### Fix\n\n- Fix: fix `IllegalFormatConversionException` because of format `AtomicLong` with `%d` on `FileDownloadModel.toString`. Closes #743\n\n## Version 1.6.4\n\n_2017-08-21_\n\n#### New Interfaces\n\n- Add `NoDatabaseImpl` for the case of some users need a no-database FileDownloader. Refs #727\n\n#### Enhancement\n\n- Import Performance: Using the `AtomicLong` instead of lock to make better efficiency on increase progressing.\n\n#### Fix\n\n- Fix: Fix response 416 http status because of the last connection range is wrong when resume downloading with the last connection has been downloaded. Closes #737\n- Fix(npe): Fix the small probability NPE crash when publish event with it has been removed on other thread. Closes #723\n\n## Version 1.6.3\n\n_2017-07-29_\n\n#### Fix\n\n- Fix: Fix the small probability occur npe when the task is calling back over status with user invoke pause simultaneously. Closes #680\n- Fix: Fix `MissingFormatArgumentException` when you pause or resume the FileDownloadserialQueue with it has already done it. Closes #699\n\n## Version 1.6.2\n\n_2017-07-16_\n\n#### Fix\n\n- Fix: Fix raise 'offset < 0' exception when FileDownloader downloading file with the one split connection range begin with larger than 1.99G. Closes #669\n\n## Version 1.6.1\n\n_2017-07-13_\n\n#### Enhancement\n\n- Import Practicability: Throw `GiveUpException` directly when the response `content-length` isn't equal to the expect `content-length` calculated from range. Closes #636\n\n#### Fix\n\n- Fix: Fix sync twice when downloading paused/error.\n- Fix: fix file is destroyed when you download chunked file from breakpoint.\n\n## Version 1.6.0\n\n_2017-07-07_\n\n#### Fix\n\n- Fix(no-response): Fix may occur no-respose when multiple connections complete fetch data simultaneously, more detail please move to [here](https://github.com/lingochamp/FileDownloader/issues/631#issuecomment-313451398). Closes #631\n\n## Version 1.5.9\n\n_2017-07-04_\n\n#### Fix\n\n- Fix(duplicate-permission): fix `INSTALL_FAILED_DUPLICATE_PERMISSION` when there are more than one application using FileDownloader lib 1.5.7 or more newer since Android 5.0. This problem is raised since v1.5.7, because of we declared permission for receiving completed status broadcast more secure, now we remove it to fix this problem. Closes #641\n\n## Version 1.5.8\n\n_2017-06-28_\n\n#### Fix\n\n- Fix(no-response): fix no-response when switch between pause and start for the same task very fast frequency. Closes #625\n\n## Version 1.5.7\n\n_2017-06-25_\n\n#### New Interfaces\n\n- Support the configuration `broadcast.completed` in `filedownloader.properties`: determine whether need post a broadcast when task is completed. Closes #605\n- Support accepting 201 http status. Closes #545\n- Support pause and resume for the `FileDownloadSerialQueue`. Closes #547\n- Handle the case of redirect(300、301、302、303、307、308) on FileDownloader self. Closes #611\n- Deprecated the `FileDownloader#init` and add the replacer `FileDownloader#setup` to let user invoke anytime before using `Filedownloader`. Closes #500\n\n> - If you want to using `broadcast.completed` and receive completed broadcast, you also need to register receiver with `filedownloader.intent.action.completed` action name on `AndroidManifest.xml` and please using `FileDownloadBroadcastHandler` class to parse the received `Intent`.\n> - Now, rather than using `FileDownloader#init`, if you want to register your own customize components for FileDownloader please invoking `FileDownloader.setupOnApplicationOnCreate(application):InitCustomMaker` on the `Application#onCreate`, otherwise you just need invoke `FileDownloader.setup(Context)` anytime before using `FileDownloader`.\n\n#### Fix\n\n- Fix: fix `FileDownloadQueueSet` can't handle the case of disable wifi-required. Thanks @qtstc\n- Fix(output-stream): fix broken support for output-stream when it don't support seek. Closes #622\n\n#### Enhancement\n\n- Improve Practicability: Cover the case of reusing the downloaded processing with the different url( using with `idGenerator` ). Closes #617\n\n## Version 1.5.6\n\n_2017-06-18_\n\n#### Fix\n\n- Fix(crash): fix raise NPE crash when require paused a task and invoking `findRunningTaskIdBySameTempPath` at the same time. Closes #613\n- Fix(crash): fix raise `IllegalArgumentException` when response code is 206 and its ETAG is changed. Closes #612\n- Fix(crash): fix raise `FileDownloadNetworkPolicyException` unhandled exception, when user enable wifi-required but no wifi-state. Thanks @qtstc\n- Fix(crash): fix raise `IllegalStateException` when user upgrades from `v1.4.3` or older version to `v1.5.2` or newer version directly and some more conditions, more detail please move to #610\n- Fix(crash): fix some small probability case raise `IllegalStateException` when callback-flow has been final but occurring completed/error at the same time.\n- Fix(no-response): fix no-response after start download and receive connected callback because the resource state has been changed during the connection of verification and connections of fetch data.\n\n#### Enhancement\n\n- Improve Practicability: callback `error` directly when create the parent directory failed. Closes #542\n- Improve Practicability: handle the case of response code is `416`. Closes #612\n\n## Version 1.5.5\n\n_2017-06-12_\n\n#### Fix\n\n- Fix(max-network-thread-count): fix the `download.max-network-thread-count` not work and there are no restrictions on the number of tasks downloaded at the same time since v1.5.0 when tasks runs on the multi-connection Closes #607\n\n## Version 1.5.4\n\n_2017-06-11_\n\n#### New Interfaces\n\n- Support customizing the download task identify generator through `IdGenerator`. Closes #224\n\n#### Enhancement\n\n- Improve Practicability: Decoupling the filedownload-database with filedownload-model, let filedownload-database only care about database operation.\n- Improve Practicability: Decoupling the database initial-maintain from the filedownload-database default implementation to let the customized database can be maintained.\n\n## Version 1.5.3\n\n_2017-06-08_\n\n#### Fix\n\n- Fix(crash): Fix divide by zero on calculating average speed when download completed and connected at the same time. Refs #601\n- Fix(crash): Fix raise NPE crash when you require pause the task between executed the fetch-data-task and fetch-data-task has not yet started. Closes #601\n\n## Version 1.5.2\n\n_2017-06-07_\n\n#### Fix\n\n- Fix(crash): Fix raising NPE crash or ConcurrentModificationException when the Task is paused or error with the connection is completing at the same time. Closes #598\n- Fix(crash): Fix raising NPE crash when pause the `FetchDataTask` and it still without any time to sync data to database or file-system. Refs #598\n- Fix(crash): Fix raising NPE crash when using the multiple connections to download and connect failed or create `FetchDataTast` failed. Refs #598\n- Fix(speed-calculate): Fix the speed result is `0` when ignore all processing callbacks and just using `FinishListener`.\n- Fix(finish-listener): Fix there isn't `over` callback for the `FinishListener` when the file has already been downloaded in the past.\n\n#### Enhancement\n\n- Improve Performance: Enable the WAL for the default sqlite to speed up sql operation because the most of our case is concurrently accessed and modified by multiple threads at the same time.\n\n## Version 1.5.1\n\n_2017-06-05_\n\n#### Fix\n\n- Fix(crash): Fix the NPE crash when don't provided the `InitCustomMaker` on `FileDownloader#init`. Closes #592\n- Fix(callback): Fix on the `pending` callback you can't get the right `sofarBytes` when there are several connections served for the task and the task is resuming from the breakpoint.\n- Fix(speed-monitor): Correct the result of the total average speed when the task resume from a breakpoint on `IDownloadSpeed.Monitor`.\n\n#### Enhancement\n\n- Improve Robust: Sync all process on fetch task manually when it is paused to make the process can be persist.\n- Improve Robust: Raise `IllegalArgumentException` when provide `context` is null on `FileDownloader.init` to expose the problem earlier.\n\n## Version 1.5.0\n\n_2017-06-05_\n\n#### New Interfaces\n\n- Support multiple-connection(multiple threads) for one downloading task.  Closes #102\n- Support `ConnectionCountAdapter` to customize connection count for each task(you can set it through `FileDownloader#init`).\n\n#### Enhancement\n\n- Improve Performance: Refactor whole download logic and origin callback logic and remove 1000 line class `FileDownloadRunnable`.\n\nThe default connection count strategy for each task, you can customize it through `ConnectionCountAdapter`:\n\n- one connection: file length [0, 1MB)\n- two connections: file length [1MB, 5MB)\n- three connections: file length [5MB, 50MB)\n- four connections: file length [50MB, 100MB)\n- five connections: file length [100MB, -]\n\n## Version 1.4.3\n\n_2017-05-07_\n\n#### Fix\n\n- Fix: Remove redundant deprecated method: `FileDownloader#init(Application)`, because `Application` is implement of `Context`.\n\n## Version 1.4.2\n\n_2017-03-15_\n\n#### Fix\n\n- Fix(Same File Path): Avoid two tasks writing to the same file simultaneously, Once there is an another running task with the same target path to the current task's, the current task will receive the `PathConflictException` to refused start downloading. Closes #471\n\n#### New Interfaces\n\n- Add `FileDownloadSerialQueue#getWaitingTaskCount`: Get the count of tasks which is waiting on the serial-queue instance. Refs #345\n\n## Version 1.4.1\n\n_2017-02-03_\n\n#### Fix\n\n- Fix(High concurrency): Fix occurring the NPE crash because of it still receiving message-snapshot in the messenger but the host task has been assigned to null since it has been received over-status message-snapshot. Closes #462\n- Fix(`FileDownloadHttpException`): Fix occurring the `IllegalStateException` because of cannot access request header fields after connection is set when occurring http-exception. Closes #458\n\n## Version 1.4.0\n\n_2017-01-11_\n\n#### Enhancement\n\n- Improve Performance: Optimize the logic in `FileDownloader#init`, let it lighter(just do some action like assign `context` and `maker`)\n\n#### Fix\n\n- Fix(pause): fix can't stop the task when occurring the high concurrency event about pausing task after starting it in very close time. Closes #402\n- Fix(init FileDownloader): fix the very low frequent crash when init FileDownloader on the process the `FileDownloadService` settled on. Closes #420  \n- Fix(FileDownloadHttpException): fix params can't match the `formatter` when occur `FileDownloadHttpException` Closes #438\n\n## Version 1.3.9\n\n_2016-12-18_\n\n### Important:\n\n- Since this version you can customize you own [FileDownloadConnection][FileDownloadConnection-java-link] component, we use [this one][FileDownloadUrlConnection-java-link] as default.\n- Since this version, FileDownloader don't dependency the okhttp as default. (If you still want to use the okhttp as your connection component, you can integrate [this repo](https://github.com/Jacksgong/filedownloader-okhttp3-connection) feel free)\n\n> If you still need configure `timeout`、`proxy` for the connection component, but you don't want to implement your own one, don't worry, I implement it for the default connection component too, just move to : [DemoApplication](https://github.com/lingochamp/FileDownloader/blob/master/demo/src/main/java/com/liulishuo/filedownloader/demo/DemoApplication.java#L35), check the code if you want.\n\n#### New Interfaces\n\n- Add `FileDownloadQueueSet#reuseAndStart`: Add reuseAndStart function to the queue-set to reuse task instances before start them. Ref #383\n- Add `FileDownloadConnection`: Support customize the connection component for FileDownloader and remove the dependency of the okhttp as default. Closes #158\n\n## Version 1.3.0\n\n_2016-10-31_\n\n#### New Interfaces\n\n- Add `FileDownloadSerialQueue`: Easy to dynamically manage tasks and tasks in the queue will automatically start download one by one. Closes #345.\n- Remove the `callback` method in the `FileDownloadListener` class, besides adding the `FileDownloadListener#isInvalid` method to tell the FileDownloader whether the listener has already invalidated, which means it can't receive any messages.\n- Add `FileDownloader#clearAllTaskData`: Clear all data in the `filedownloader` database. Closes #361\n\n#### Enhancement\n\n- Improve Practicability(`FileDownloadListener#blackCompleted`): Ensure the `blockCompleted` callback method can accept any `Exception`. Closes #369.\n- Improve Practicability(service-not-connected): Print the tips with the cause in service-not-connected-helper, in this way, when you invoke some methods need the FileDownload service has already connected but not yet, FileDownloader will not only print causes in the `Logcat` but also print the tips.\n\n#### Fix\n\n- Fix(reuse): fix `BaseDownloadTask#reuse` is called shortly after the call to `BaseDownloadTask#pause` may raise an exception. Closes #329.\n\n## Version 1.2.2\n\n_2016-10-15_\n\n#### Fix\n\n- Fix(fatal-crash): fix when the task doesn't have `FileDownloadListener`, we can't receive the callback of `FileDownloadMonitor.IMonitor#onTaskOver` for it. Closes #348.\n\n## Version 1.2.1\n\n_2016-10-09_\n\n#### Fix\n\n- <s>Fix(fatal-crash): fix when the task doesn't have `FileDownloadListener`, we can't receive the callback of `FileDownloadMonitor.IMonitor#onTaskOver` for it. Closes #348. </s> Sorry for my mistake, this bug is still exist in 1.2.1 and finally fixed in 1.2.2.\n\n## Version 1.2.0\n\n_2016-10-04_\n\n#### New Interfaces\n\n- Add `FileDownloader#insureServiceBind()`: Easy to block the current thread, and start FileDownloader service, after the service started then executes the request which needs the service alive. Refs #324.\n- Add `FileDownloader#insureServiceBindAsync()`: Easy to start FileDownloader service, and after the service started then executes the request which needs the service alive. Refs #324.\n- Add `FileDownloader#bindService(runnable:Runnable)`: Easy to start FileDownloader service, and after the service started then executes the `runnable`. Refs #324.\n- Add `FileDownloader#init(Context,InitCustomMaker)`: Easy to initialize the FileDownloader engine with various kinds of customized components.\n\n#### Enhancement\n\n- Improve Practicability(`InitCustomMaker#database`): Support customize the database component with the implementation of `FileDownloadDatabase`, and implements the default database component: `DefaultDatabaseImpl`.\n- Improve Practicability(`InitCustomMaker#outputStreamCreator`): Support customize the output stream with the implementation of `FileDownloadOutputStream`, and implements the default output stream component `FileDownloadRandomAccessFile`, and some alternative components: `FileDownloadBufferedOutputStream`、`FileDownloadOkio`.\n\n## Version 1.1.5\n\n_2016-09-29_\n\n#### New Interfaces\n\n- Support the configuration `file.non-pre-allocation` in `filedownloader.properties`: Whether doesn't need to pre-allocates the 'content-length' space when to start downloading, default is `false`. Closes #313 .\n\n#### Fix\n\n- Fix(fatal-crash): fix occur the `StackOverflowError` when thread pool getActiveCount is not right because of it just an approximate number. Closes #321 .\n- Fix(minor-crash): fix in some minor cases occur `IllegalStateException` which message is 'No reused downloaded file in this message'. Closes #316 .\n- Fix(minor-crash): fix when there are several serial-queues started in case of the FileDownloader service doesn't connect yet and in minor cases that the same task in the queue will be started twice which lead to crash. Refs #282 .\n\n#### Others\n\n- Dependency: Cancel the dependence of thread-pool library. Refs #321 .\n- MinSDKVersion: Upgrade `minSdkVersion` : 8->9. Refs #321 .\n\n## Version 1.1.0\n\n_2016-09-13_\n\n#### New Interfaces\n\n- Add `BaseDownloadTask#setWifiRequired`: Set whether the task only allows downloading on the wifi network type. Default `false`. Closes #281 .\n\n#### Enhancement\n\n- Improve Performance: Alternate all thread pools to exceed-wait-pool(more detail: docs in `FileDownloadExecutors`) and all threads in pools will be terminate after idle 5 second. Refs #303 .\n- Improve Practicability: Handle any `Throwable`s thrown on `FileDownloadListener#blockComplete` method and callback to `FileDownloadListener#error` method instead of `FileDownloadListener#completed`. Closes #305 .\n\n#### Fix\n\n- Fix(lost-connect): Prevent the waiting-connect-list contains duplicate tasks in minor cases.\n\n## Version 1.0.2\n\n_2016-09-06_\n\n#### Fix\n\n- Fix: When the service didn't connected and now it is connected and FileDownloader try to restart the 'queue-task's which in the waiting-service-connect list but occur an `IllegalStateException`. Closes #307 .\n\n## Version 1.0.1\n\n_2016-09-05_\n\n#### New Interfaces\n\n> If you used `BaseDownloadTask#ready()` which is a deprecated method now, just migrate it to `BaseDownloadTask#asInQueueTask():InQueueTask` and `InQueueTask#enqueue()`.\n\n- Add `BaseDownloadTask#asInQueueTask():InQueueTask` and Deprecated `BaseDownloadTask#ready()`: Declare the task is a queue task, what will be assembled by a queue which makes up of the same `listener` task and there is a method `InQueueTask#enqueue()` to enqueue this task to the global queue to ready for being assembled by the queue. The operation of method `InQueueTask#enqueue()` is the same to the Deprecated method `BaseDownloadTask#ready()`, we wrap the `ready()` method in this way just want you to know clearly: Only if the task belongs to a queue, you need to invoke this method otherwise if this task is an isolated task but you invoke this method, it's wrong and you will receive an exception(More detail reason please move to the exception thrown in `DownloadTask#start`).\n\n#### Fix\n\n- Fix: Maybe occur an IllegalStateException when there are several isolated tasks and queues with the same `listener` object, and they are started in the different thread simultaneously. Closes #282 .\n\n## Version 1.0.0\n\n_2016-08-21_\n\n#### New Interfaces\n\n- Add `BaseDownloadTask#cancel`: This method is used for explaining why the pause operation is the same as the cancel operation.\n\n#### Enhancement\n\n- Improve Performance: Hold the result of `isDownloaderProcess`.\n- Improve Practicability: Refactor the visible layer of the code. Closes #283\n- Improve Practicability: Perfect the java doc. Closes #284\n- Improve Practicability: Add the java doc website: http://fd.dreamtobe.cn. Closes #285\n\n## Version 0.3.5\n\n_2016-08-16_\n\n#### Enhancement\n\n- Improve Practicability: Add thread name to all threads used in FileDownloader.\n- Improve Performance: Change the count of core thread for block-completed-thread-pool: 5->2, reduce redundant resource waste.\n\n#### Fix\n\n- Fix(SQLiteFullException): Cover the case of SQLiteFullException during the entire downloading process, and ensure the exception can be carried back to `FileDownloadListener#error` . Closes #243\n- Fix(directory-case): Fix in the case of the provided path is a directory, and the task already completed, if you start the task again you will receive `FileDownloadListener#completed` directly, but the `targetFilePath` may be null in the `FileDownloadListener#completed` callback method. Closes #237\n\n## Version 0.3.4\n\n_2016-07-31_\n\n#### New Interfaces\n\n- Add `FileDownloader#clear`: clear the data with the task id in the filedownloader database. Closes #218.\n\n#### Enhancement\n\n- Improve Practicability: Add return value to the method `FileDownloader#start(FileDownloadListener, boolean)` : Whether start tasks successfully. Closes #215.\n- Improve Practicability: Pause tasks with the same download-id rather than just pause one task through there are more than one task in downloading.\n\n#### Fix\n\n- Fix(init-crash): Fix the crash about the list of running-app-process-info from `ActivityManager` is null when to init FileDownloader. Closes #210.\n- Fix(minor-crash): Fix the NPE-crash when to execute receiving snapshot-message after FileDownloadService already onDestroy. Closes #213.\n- Fix(message-keep-flow): Delete the target file before start downloading, ensure can't get the `completed` status when another same task is downloading. Closes #220\n- Fix(start-serial): Assemble non-attached-tasks to start rather than assemble tasks just refer to FileDownloadListener, fix no possibility to start two queues with the same `FileDownloadListener`. Closes #223.\n- Fix(free-messenger): Free the messenger of Task before call back 'over-message' to FileDownloadListener instead of after callback, ensure Task can be reused in FileDownloadListener callback method. Closes #229.\n\n#### Others\n\n- Upgrade dependency okhttp from `3.3.1` to `3.4.1`.\n\n\n## Version 0.3.3\n\n_2016-07-10_\n\n#### New Interfaces\n\n- Add `FileDownloadUtils#getTempPath`: Get the temp path is used for storing the temporary file not completed downloading yet(`filename.temp`). Refs #172.\n- Add `FileDownloader#getStatusIgnoreCompleted(id:int)`:  Get the downloading status without cover the completed status(If completed you will receive `INVALID`).\n- Add `FileDownloader#getStatus(id:int, path:String)`:  Get the downloading status.\n- Add `FileDownloader#getStatus(url:String, path:String)`:  Get the downloading status.\n- Add `FileDownloadUtils#isFilenameConverted(context:Context)`: Whether tasks from FileDownloader Database has converted all files' name from `filename`(in old architecture) to `filename.temp`, if it is not completed downloading yet.\n- Add `FileDownloadUtils#generateId(url:String, path:String, pathAsDirectory:boolean)`: Generate a `Download Id` which can be recognized in FileDownloader.\n- Add `BaseDownloadTask#setPath(path:String, pathAsDirectory:boolean)`: If `pathAsDirectory` is `true`, the `path` would be the absolute directory to store the downloading file, and the `filename` will be found in `contentDisposition` from the `response#header` as default.\n- Add `BaseDownloadTask#isPathAsDirectory`: Whether the result of `BaseDownloadTask#getPath()` is a `directory` path or `directory/filename` path.\n- Add `BaseDownloadTask#getTargetFilePath`: Get the target file path to store the downloading file.\n- Add `FileDownloadQueueSet#setDirectory`: Set the `directory` to store files in this queue.\n\n#### Enhancement\n\n- Improve Practicability: Support the `path` of the task as the directory to store the file, and in this case, the `filename` will be found in `contentDisposition` from the `response#header` as default. Refs #200.\n- Improve Practicability: Using the temp path to store the file not completed downloading yet(`filename.temp`). Refs #172.\n- Improve Performance: FileDownloader doesn't store completed tasks in Database anymore, and check whether the task has completed downloading with `File#exists()` directly. Refs #176, #172.\n- Improve Robust: Choosing the task which status is `INVALID` or `progress` to receive `completed` message preferentially, to ensure the callback of `progress` can be handled. Refs #123\n- Improve Robust: Expanding task-sync-lock to the outside of getting-same-id-downloading-task, to fix some messages can't be consumed because status changed during getting-same-id-downloading-task and waiting for task-sync-lock.\n\n#### Fix\n\n- Fix(DB-maintain): Keeping models, whose status is `pending` and downloaded so far bytes is more than 0 because it can be used for resuming from the breakpoint. Closes #176.\n- Fix(crash-NPE): FileDownloader might occur NPE when the download-listener was removed, but the task is still running in FileDownloader. Closes #171.\n\n## Version 0.3.2\n\n_2016-06-12_\n\n#### New Interfaces\n\n- Add `BaseDownloadTask#setCallbackProgressMinInterval`: Set the minimum time interval between each callback of 'progress'. Closes #167.\n- Add `FileDownloader#setMaxNetworkThreadCount`: Change the number of simultaneous downloads(the number of the simultaneously running network threads) at the code side. Closes #168.\n- Add `FileDownloader#init(Context,OkHttpClientCustomMaker,int)`: Accept initializing the number of simultaneous downloads(the number of the simultaneously running network threads) with the FileDownloadService initializes. Closes #168.\n\n#### Enhancement\n\n- Improve Robust: Ensure the minimum time interval between each callback of 'progress' is 5ms, To prevent internal callback of 'progress' too frequent happening. Closes #167.\n- Improve Practicability: Print the 'warn' priority log when a request does something in the FileDownloadService but it isn't connected yet.\n- Improve Performance: Using the `SparseArray` instead of `HashMap` for mapping all `FileDownloadModel`.\n\n#### Fix\n\n- Fix(crash): Fix provided wrong params in formatting character string when to starting download runnable occur the unexpected downloading status.\n- Fix(force-re-download): Fix the wrong logic: In the case of `BaseDownloadTask#setForceReDownload(true)` and the task has already downloaded will trigger 'warn' callback. Closes #169 .\n- Fix(class-type): Keep the class type of `SocketTimeOutException`, and no longer care about whether the message of Throwable is empty, this is very redundant.\n\n#### Others\n\n- Upgrade dependency okhttp from `3.2.0` to `3.3.1`.\n\n## Version 0.3.1\n\n_2016-05-19_\n\n#### Enhancement\n\n- Improve Robust: Ensuring buffer is written out to the device when at the end of fetching data.\n\n## Version 0.3.0\n\n_2016-05-13_\n\n#### Fix\n\n> Why FileDownload can run in UI process? Ref [filedownloader.properties](https://github.com/lingochamp/FileDownloader/wiki/filedownloader.properties).\n\n- Fix(shared-UI-process): fix the addition header does not attach to Http-request when the FileDownload service isn't running in the separate process to UI process. Closes #149.\n\n\n## Version 0.2.9\n\n_2016-05-10_\n\n#### New Interfaces\n\n- Add `BaseDownloadTask#isUsing():boolean`: Whether this task object has already started and used in FileDownload Engine. Closes #137 .\n\n#### Fix\n\n- Fix(high-concurrency-npe): Providing the default snapshot when a task's status is unexpected, preventing the npe is occurred in this case.\n- Fix(response-416): Covering the response status code is 416 or still resume from breakpoint when its so far bytes more than or equal to total bytes.\n\n## Version 0.2.8\n\n_2016-05-02_\n\n#### New Interfaces\n\n- Add `BaseDownloadTask#getId():int`: deprecate `getDownloadId()`, and using the `getId()` instead, for `BaseDownloadTask`.\n\n#### Enhancement\n\n- Improve Robust: Refactor the launcher for launching tasks more make sense, and expire tasks with listener or expire all waiting-tasks more stable.\n- Improve Robust: Refactor the architecture which is used to handle the event send to `FileDownloadListener`, the new architecture just like a messenger and message-station, each tasks would write snapshot messages to message-station.\n- Improve Robust: Cover all high concurrent situations about pausing a task, remove some expected warn logs about it.\n- Improve Performance: Reduce the FileDownloader database I/O.\n- Improve Performance: Reduce creating object(less allocating memory request, friendly to GC) for each call-back, Taking a message snapshot for a status updating, and through whole communication architecture just use it.\n\n#### Fix\n\n- Fix: Provide the definite locale for formatting strings, prevent unexpected-locale as Default happening. Closes #127\n\n## Version 0.2.7\n\n_2016-04-22_\n\n#### New Interfaces\n\n- Add `FileDownloader#setTaskCompleted(taskAtomList:List<FileDownloadTaskAtom>)`: Used to telling the FileDownloader Engine that a bulk of tasks have already downloaded by other ways.\n\n#### Enhancement\n\n- Improve Robust: Throw the Fatal-Exception directly when request to bind the FileDownloadService in the `:filedownloader` process. Closes #119 .\n\n## Version 0.2.6\n\n_2016-04-20_\n\n#### New Interfaces\n\n- Adjust: Change the location of the `filedownloader.properties` ，no more in the root directory of project, instead below the `assets` of a module, for example `/demo/src/main/assets/filedownloader.properties`.\n\n#### Fix\n\n- Fix: `filedownloader.properties` not work. Closes #117.\n\n## Version 0.2.5\n\n_2016-04-19_\n\n#### New Interfaces\n\n- Add `FileDownloader#setTaskCompleted`: Used to telling the FileDownloader Engine that the task with the url and the path has already completed downloading by other ways(not by FileDownloader Engine).\n- Support the configuration `download.max-network-thread-count` in `filedownloader.properties`: The maximum network thread count for downloading simultaneously, default is 3. Closes #116.\n\n## Version 0.2.4\n\n_2016-04-18_\n\n#### New Interfaces\n\n- Add `BaseDownloadTask#getSpeed` and `BaseDownloadTask#setMinIntervalUpdateSpeed`: Get the download speed for a task. If it is in processing, the speed would be real-time speed; If finished, the speed would be average speed. Closes #95\n- Add the `FileDownloader#startForeground` and `FileDownloader#stopForeground` for supporting the Foreground mode([Service#startForeground](https://github.com/lingochamp/FileDownloader/wiki/filedownloader.properties)); For ensure the FileDownloadService would keep alive when user removed the App from the recent apps. Closes #110 .\n- Support configurations `download.min-progress-step` and `download.min-progress-time`: The min buffered so far bytes and millisecond, used for adjudging whether is time to sync the download so far bytes to database and make sure sync the downloaded buffers to the local file. More small more frequent, then download more slowly, but will safer in the scene of the process is killed unexpectedly. Default 65536(MinProgressStep) and 2000(MinProgressTime), which follow the value in `com.android.providers.downloads.Constants`.\n- Support the configuration `process.non-separate` in `filedownloader.properties`: The FileDownloadService runs in the separate process ':filedownloader' as default, if you want to run the FileDownloadService in the main process, set this configuration as `true`. Closes #106 .\n\n#### Enhancement\n\n- Improve Performance: Download more quickly, Optimize the strategy about sync the buffered datum to database and local file when processing. Closes #112 .\n\n#### Fix\n\n- Fix: Can't restart the task which in paused but is still settling in the download-pool. Closes #111\n\n## Version 0.2.3\n\n_2016-04-11_\n\n#### New Interfaces\n\n- Add `FileDownloadOutOfSpaceException`, Throw this exception, when the file will be downloaded is too large to store.\n- Add new call-back method in `FileDownloadListener`: `started` which will be invoked when finish pending, and start the download runnable.\n- Add new call-back method in `FileDownloadMonitor.IMonitor`: `onTaskStarted` which will be invoked when finish pending, and start the download runnable.\n\n#### Enhancement\n\n- Improve Practicability: Provide the current task to the method `over` in `FinishListener`, for recognizing target task in case of one-FinishListener for more than one task. Closes #69 .\n- Improve Robust: Throw the exception directly when invoke `BaseDownloadTask#start` for a running-task object, add provide 'reuse' method to reuse a used and already finished task object. Closes #91 .\n- Improve Performance: Intercept the enqueue operate for the otiose event which is no listener for handling it.\n\n#### Fix\n\n- Fix: In handful cases the task-call-back flow not follow the expect.\n- Fix: `progress` call-back included the ending frame ( `sofarBytes == totalBytes` ).\n- Fix: Carry back the total bytes in the status of warn, for covering the case of UI-process had killed but has restarted App with restarting the task and download-process is alive still, the total bytes is 0 in UI-process. Closes #90 .\n- Fix: Can't call-back 'retry' in expect, the case of the call-back method 'retry' one-by-one. Refs: #91 .\n- Fix: The wrong sofar bytes will cover the right one, when occur error in no-network and has chance to retry. Closes #92 .\n- Fix: Handle the case of the downloading is finished during the 'check-reuse' to 'check-downloading' in filedownloader-process.\n- Fix: The serial-queue converts to The parallel-queue in restoring from filedownloader-process has killed and restarting.\n\n## Version 0.2.2\n\n_2016-04-06_\n\n#### New Interfaces\n\n- Add `FileDownloadHttpException` and `FileDownloadGiveUpRetryException`, and optimize the mechanism of exception. Closes #67 .\n- Init the `FileDownloader` use `Context` instead of `Application` ( `FileDownloader#init(Context)` ) , for more make sense and unit-test. Closes #54 .\n\n#### Enhancement\n\n- Improve Robust: Check whether free space is enough, and throw IOException directly when not enough; And pre-allocate need-available-space before fetching datum when the free space more than need-available-space. Closes #46 .\n- Improve Practicability: Support resume from breakpoint without ETag. Just need the server support the request-header param 'Range'. Close #35 , #66 .\n\n\n#### Fix\n\n- Fix: The `IllegalFormatConversionException` on `EventPool` when publishing the event which does not in effect and `FileDownloadLog.NEED_LOG` is `true`. Closes #30 .\n- Fix: The non-fatal-crash in `IFileDownloadIPCService.java` , when lost connection from filedownloader process. because the IBinder's hosting process(filedownloader process) has been killed/cancelled. Closes #38 .\n- Fix: The leak of response-body: 'WARNING: A connection to https://... was leaked. Did you forget to close a response body?' Closes #68 .\n- Fix: Using the internal-string as synchronized lock-object instead of string-original.\n- Fix: The number of the Ing-call-back is not correct in some cases.\n\n#### Others\n\n- Upgrade dependency okhttp from `3.1.2` to `3.2.0`.\n\n## Version 0.2.0\n\n_2016-02-15_\n\n#### New Interfaces\n\n- `filedownloader.properties-http.lenient`: Add 'filedownloader.properties' for some special global configs, and add 'http.lenient' keyword to 'filedownloader.properties' to handle the case of want to ignore HTTP response header from download file server isn't legal.\n- `FileDownloadNotificationHelper`: Refashioning NotificationHelper, let handle notifications with FileDownloader more make sense. #25\n- `FileDownloader#init(Application,OkHttpClientCustomMaker)`: Support customize OkHttpClient which will be used for downloading files.\n\n#### Fix\n\n- Fix: Occur 'Concurrent Modification Exception' when Downloader service is unbound or lost connection to service and NeedRestart list not empty. #23\n- Fix: The case of re-connect from lost connection to service but all auto restart tasks' call-back do not effect.\n- Fix: In some cases of high concurrency, the Pause on some tasks is no effect.\n\n## Version 0.1.9\n\n_2016-01-23_\n\n> FileDownloader is enable Avoid Missing Screen Frames as default, if you want to disable it, please invoke `FileDownloader.getImpl().disableAvoidDropFrame()`.\n\n#### New Interfaces\n\n> We default open Avoid Missing Screen Frames, if you want to disable it(will post to ui thread for each FileDownloadListener event achieved as pre version), please invoke: `FileDownloader.getImpl().disableAvoidDropFrame()`.\n\n- `FileDownloadMonitor`: You can add the global monitor for Statistic/Debugging now.\n- `FileDownloader#enableAvoidDropFrame(void)`: Avoid missing screen frames, but this leads to all callbacks of FileDownloadListener do not be invoked at once when it has already achieved.\n- `FileDownloader#disableAvoidDropFrame(void)`: Disable avoid missing screen frames, let all callbacks of FileDownloadListener be invoked at once when it achieve.\n- `FileDownloader#isEnabledAvoidDropFrame(void)`: Has already enabled Avoid Missing Screen Frames. Default: true\n- `FileDownloader#setGlobalPost2UIInterval(intervalMillisecond:int)`: For Avoid Missing Screen Frames. Each intervalMillisecond post 1 message to ui thread at most. if the value is less than 0, each callback will always post a message to ui thread immediately, may will cause missing screen frames and produce great pressure on the ui thread Looper. Default: 10ms.\n- `FileDownloader#setGlobalHandleSubPackageSize(packageSize:int)`: For Avoid Missing Screen Frames. {packageSize}: The number of FileDownloadListener's callback contained in each message. value completely dependent on the intervalMillisecond of setGlobalPost2UIInterval, describe will handle up to {packageSize} callbacks on the each message posted to ui thread. Default: 5.\n- `BaseDownloadTask#setSyncCallback(syncCallback:boolean)`: if true will invoke callbacks of FileDownloadListener directly on the download thread(do not post the message to the ui thread), default false.\n- `BaseDownloadTask#isSyncCallback(void):boolean`: Whether sync invoke callbacks of FileDownloadListener directly on the download thread.\n- `FileDownloadUtils#setDefaultSaveRootPath`: The path is used as Root Path in the case of task without setting path in the entire Download Engine.\n- `FileDownloadQueueSet`: In order to be more convenient to bind multiple tasks to a queue, and to the overall set.\n\n#### Enhancement\n\n- Improve Debugging: Provide the `FileDownloadMonitor` to monitor entire Download Engine.\n- Improve Performance: Optimize EventPool lock & do not handle listener priority any more(no use internal).\n- Improve Performance: Call `FileDownloadListener` methods do not through EventPool, instead, invoke directly.\n\n#### Fix\n\n- Fix: EventPool listener unlimited increased bug.\n\n## Version 0.1.5\n\n_2016-01-17_\n\n#### New Interfaces\n\n- `BaseDownloadTask#setTag(key:int, tag:Object)`: Set a tag associated with this task. If the key already existed, the old tag will be replaced.\n- `BaseDownloadTask#getTag(key:int)`: Get the object stored in the task as a tag, or null if not set.\n- `BaseDownloadTask#addHeader(name:String, values:String)`: Add custom request header to the task. Attention: We have already handled ETag, and will add `If-Match` & `Range` value if it works.\n- `BaseDownloadTask#addHeader(line:String)`: Add custom request header to the task. Attention: We have already handled ETag, and will add `If-Match` & `Range` value if it works.\n- `BaseDownloadTask#removeAllHeaders(name:String)`: Remove all custom request header bind with the `{name}`.\n\n#### Enhancement\n\n- Improve Performance: Reduce the consumption of the generated log.\n- Improve Debugging: To filter all the log level, reduce the high level of log output, and by default, will output `Warn`、`Error`、`Assert` level of log in order to debugging in the case of the value of `FileDownloadLog.NEED_LOG` is false(default).\n\n#### Fix\n\n- Fix can't resume from the break point naturally in case of the download status of the task is Error.\n- Fix the size of the queue may not match the number of actual active tasks in case of high concurrency. This bug may would caused some callbacks to be consumed by the old tasks.\n\n#### Others\n\n- Upgrade dependency okhttp from `2.7.1` to `3.0.1`.\n\n## Version 0.1.4\n\n_2016-01-13_\n\n#### New Interfaces\n\n- `FileDownloader#unBindServiceIfIdle(void)`: If there is no active task in the `:filedownloader` progress currently , then unbind & stop `:filedownloader` process\n- `FileDownloader#getStatus(downloadId)`: Get download status by the downloadId(ps: Please refer to [Tasks Manager demo](https://github.com/lingochamp/FileDownloader/blob/master/demo/src/main/java/com/liulishuo/filedownloader/demo/TasksManagerDemoActivity.java)\n- `FileDownloader#isServiceConnected(void)`: Whether started and connected to the `:filedownloader` progress(ps: Please refer to [Tasks Manager demo](https://github.com/lingochamp/FileDownloader/blob/master/demo/src/main/java/com/liulishuo/filedownloader/demo/TasksManagerDemoActivity.java))\n\n#### Enhancement\n\n- Supported [Chunked transfer encoding](https://en.wikipedia.org/wiki/Chunked_transfer_encoding) data download(Recommend to glance at demo on [Single Task Test](https://github.com/lingochamp/FileDownloader/blob/master/demo/src/main/java/com/liulishuo/filedownloader/demo/SingleTaskTestActivity.java)).\n- Improve Performance: Reduce IPC.\n- Improve Performance: Reduce lock.\n- Improve Performance: Delete invalid datum in db with the `:filedownloader` progress start.\n- Improve Performance: Ignore the `callbackProgressTimes` column in db.\n\n#### Fix\n\n- Fix `FileDownloader#pauseAll` not effect in case of low memory and ui progress is Background Progress situation and the `:filedownloader` progress(Service Progress) alive and still have running tasks in the `filedownloader` progress but ui progress has died and relived.\n- Fix not release connect resources when invoke `FileDownloader#unBinderService` manually.\n- Handle case of ui progress be killed by sys and download progress not be killed, and ui progress relives and re-executes same tasks queue.\n\n\n## Version 0.1.3\n\n_2016-01-04_\n\n- Enhancement: no longer subject to the upper bound of 1.99G, add `FileDownloadLargeFileListener`, `getLargeFileSoFarBytes()`,`getLargeFileTotalBytes()`.\n- Performance optimization: some ipc transaction just need one-way call(async), not block(sync).\n- Upgrade dependency okhttp from `2.7.0` to `2.7.1`.\n\n## Version 0.1.2\n\n_2015-12-27_\n\n- Optimize thread digestion([map](https://github.com/lingochamp/FileDownloader/raw/master/art/filedownload_sample_description.png).\n- Fix: may `pause()` invalid in large queue task.\n- Fix: large queue task parallel download, may download has been completed but the callback\n\n## Version 0.1.1\n\n_2015-12-25_\n\n- Optimization of internal performance, according to the time split thread pool.\n- Add auto retry feature.\n\n## Version 0.1.0\n\n_2015-12-24_\n\n- The `FileDownloadStatus` parameter type is changed from `int` to `byte`, which is frequently copied in IPC.\n- Optimization of multi task queue filtering time.\n- Optimizing serial task execution mechanism.\n\n## Version 0.0.9\n\n_2015-12-23_\n\n- The start operation into independent thread processing, sharing thread pool in EventPool.\n\n## Version 0.0.8\n\n_2015-12-22_\n\n- initial release\n\n[RemitDatabase-png]: https://github.com/lingochamp/FileDownloader/raw/master/art/remit-database.png\n[FileDownloadConnection-java-link]: https://github.com/lingochamp/FileDownloader/blob/master/library/src/main/java/com/liulishuo/filedownloader/connection/FileDownloadConnection.java\n[FileDownloadUrlConnection-java-link]: https://github.com/lingochamp/FileDownloader/blob/master/library/src/main/java/com/liulishuo/filedownloader/connection/FileDownloadUrlConnection.java\n"
  },
  {
    "path": "LICENSE.txt",
    "content": "\n                                 Apache License\n                           Version 2.0, January 2004\n                        http://www.apache.org/licenses/\n\n   TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION\n\n   1. Definitions.\n\n      \"License\" shall mean the terms and conditions for use, reproduction,\n      and distribution as defined by Sections 1 through 9 of this document.\n\n      \"Licensor\" shall mean the copyright owner or entity authorized by\n      the copyright owner that is granting the License.\n\n      \"Legal Entity\" shall mean the union of the acting entity and all\n      other entities that control, are controlled by, or are under common\n      control with that entity. For the purposes of this definition,\n      \"control\" means (i) the power, direct or indirect, to cause the\n      direction or management of such entity, whether by contract or\n      otherwise, or (ii) ownership of fifty percent (50%) or more of the\n      outstanding shares, or (iii) beneficial ownership of such entity.\n\n      \"You\" (or \"Your\") shall mean an individual or Legal Entity\n      exercising permissions granted by this License.\n\n      \"Source\" form shall mean the preferred form for making modifications,\n      including but not limited to software source code, documentation\n      source, and configuration files.\n\n      \"Object\" form shall mean any form resulting from mechanical\n      transformation or translation of a Source form, including but\n      not limited to compiled object code, generated documentation,\n      and conversions to other media types.\n\n      \"Work\" shall mean the work of authorship, whether in Source or\n      Object form, made available under the License, as indicated by a\n      copyright notice that is included in or attached to the work\n      (an example is provided in the Appendix below).\n\n      \"Derivative Works\" shall mean any work, whether in Source or Object\n      form, that is based on (or derived from) the Work and for which the\n      editorial revisions, annotations, elaborations, or other modifications\n      represent, as a whole, an original work of authorship. For the purposes\n      of this License, Derivative Works shall not include works that remain\n      separable from, or merely link (or bind by name) to the interfaces of,\n      the Work and Derivative Works thereof.\n\n      \"Contribution\" shall mean any work of authorship, including\n      the original version of the Work and any modifications or additions\n      to that Work or Derivative Works thereof, that is intentionally\n      submitted to Licensor for inclusion in the Work by the copyright owner\n      or by an individual or Legal Entity authorized to submit on behalf of\n      the copyright owner. For the purposes of this definition, \"submitted\"\n      means any form of electronic, verbal, or written communication sent\n      to the Licensor or its representatives, including but not limited to\n      communication on electronic mailing lists, source code control systems,\n      and issue tracking systems that are managed by, or on behalf of, the\n      Licensor for the purpose of discussing and improving the Work, but\n      excluding communication that is conspicuously marked or otherwise\n      designated in writing by the copyright owner as \"Not a Contribution.\"\n\n      \"Contributor\" shall mean Licensor and any individual or Legal Entity\n      on behalf of whom a Contribution has been received by Licensor and\n      subsequently incorporated within the Work.\n\n   2. Grant of Copyright License. Subject to the terms and conditions of\n      this License, each Contributor hereby grants to You a perpetual,\n      worldwide, non-exclusive, no-charge, royalty-free, irrevocable\n      copyright license to reproduce, prepare Derivative Works of,\n      publicly display, publicly perform, sublicense, and distribute the\n      Work and such Derivative Works in Source or Object form.\n\n   3. Grant of Patent License. Subject to the terms and conditions of\n      this License, each Contributor hereby grants to You a perpetual,\n      worldwide, non-exclusive, no-charge, royalty-free, irrevocable\n      (except as stated in this section) patent license to make, have made,\n      use, offer to sell, sell, import, and otherwise transfer the Work,\n      where such license applies only to those patent claims licensable\n      by such Contributor that are necessarily infringed by their\n      Contribution(s) alone or by combination of their Contribution(s)\n      with the Work to which such Contribution(s) was submitted. If You\n      institute patent litigation against any entity (including a\n      cross-claim or counterclaim in a lawsuit) alleging that the Work\n      or a Contribution incorporated within the Work constitutes direct\n      or contributory patent infringement, then any patent licenses\n      granted to You under this License for that Work shall terminate\n      as of the date such litigation is filed.\n\n   4. Redistribution. You may reproduce and distribute copies of the\n      Work or Derivative Works thereof in any medium, with or without\n      modifications, and in Source or Object form, provided that You\n      meet the following conditions:\n\n      (a) You must give any other recipients of the Work or\n          Derivative Works a copy of this License; and\n\n      (b) You must cause any modified files to carry prominent notices\n          stating that You changed the files; and\n\n      (c) You must retain, in the Source form of any Derivative Works\n          that You distribute, all copyright, patent, trademark, and\n          attribution notices from the Source form of the Work,\n          excluding those notices that do not pertain to any part of\n          the Derivative Works; and\n\n      (d) If the Work includes a \"NOTICE\" text file as part of its\n          distribution, then any Derivative Works that You distribute must\n          include a readable copy of the attribution notices contained\n          within such NOTICE file, excluding those notices that do not\n          pertain to any part of the Derivative Works, in at least one\n          of the following places: within a NOTICE text file distributed\n          as part of the Derivative Works; within the Source form or\n          documentation, if provided along with the Derivative Works; or,\n          within a display generated by the Derivative Works, if and\n          wherever such third-party notices normally appear. The contents\n          of the NOTICE file are for informational purposes only and\n          do not modify the License. You may add Your own attribution\n          notices within Derivative Works that You distribute, alongside\n          or as an addendum to the NOTICE text from the Work, provided\n          that such additional attribution notices cannot be construed\n          as modifying the License.\n\n      You may add Your own copyright statement to Your modifications and\n      may provide additional or different license terms and conditions\n      for use, reproduction, or distribution of Your modifications, or\n      for any such Derivative Works as a whole, provided Your use,\n      reproduction, and distribution of the Work otherwise complies with\n      the conditions stated in this License.\n\n   5. Submission of Contributions. Unless You explicitly state otherwise,\n      any Contribution intentionally submitted for inclusion in the Work\n      by You to the Licensor shall be under the terms and conditions of\n      this License, without any additional terms or conditions.\n      Notwithstanding the above, nothing herein shall supersede or modify\n      the terms of any separate license agreement you may have executed\n      with Licensor regarding such Contributions.\n\n   6. Trademarks. This License does not grant permission to use the trade\n      names, trademarks, service marks, or product names of the Licensor,\n      except as required for reasonable and customary use in describing the\n      origin of the Work and reproducing the content of the NOTICE file.\n\n   7. Disclaimer of Warranty. Unless required by applicable law or\n      agreed to in writing, Licensor provides the Work (and each\n      Contributor provides its Contributions) on an \"AS IS\" BASIS,\n      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or\n      implied, including, without limitation, any warranties or conditions\n      of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A\n      PARTICULAR PURPOSE. You are solely responsible for determining the\n      appropriateness of using or redistributing the Work and assume any\n      risks associated with Your exercise of permissions under this License.\n\n   8. Limitation of Liability. In no event and under no legal theory,\n      whether in tort (including negligence), contract, or otherwise,\n      unless required by applicable law (such as deliberate and grossly\n      negligent acts) or agreed to in writing, shall any Contributor be\n      liable to You for damages, including any direct, indirect, special,\n      incidental, or consequential damages of any character arising as a\n      result of this License or out of the use or inability to use the\n      Work (including but not limited to damages for loss of goodwill,\n      work stoppage, computer failure or malfunction, or any and all\n      other commercial damages or losses), even if such Contributor\n      has been advised of the possibility of such damages.\n\n   9. Accepting Warranty or Additional Liability. While redistributing\n      the Work or Derivative Works thereof, You may choose to offer,\n      and charge a fee for, acceptance of support, warranty, indemnity,\n      or other liability obligations and/or rights consistent with this\n      License. However, in accepting such obligations, You may act only\n      on Your own behalf and on Your sole responsibility, not on behalf\n      of any other Contributor, and only if You agree to indemnify,\n      defend, and hold each Contributor harmless for any liability\n      incurred by, or claims asserted against, such Contributor by reason\n      of your accepting any such warranty or additional liability.\n\n   END OF TERMS AND CONDITIONS\n\n   APPENDIX: How to apply the Apache License to your work.\n\n      To apply the Apache License to your work, attach the following\n      boilerplate notice, with the fields enclosed by brackets \"[]\"\n      replaced with your own identifying information. (Don't include\n      the brackets!)  The text should be enclosed in the appropriate\n      comment syntax for the file format. We also recommend that a\n      file or class name and description of purpose be included on the\n      same \"printed page\" as the copyright notice for easier\n      identification within third-party archives.\n\n Copyright (c) 2015 LingoChamp Inc.\n\n Licensed under the Apache License, Version 2.0 (the \"License\");\n you may not use this file except in compliance with the License.\n You may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\n Unless required by applicable law or agreed to in writing, software\n distributed under the License is distributed on an \"AS IS\" BASIS,\n WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n See the License for the specific language governing permissions and\n limitations under the License.\n"
  },
  {
    "path": "README-zh.md",
    "content": "# FileDownloader\nAndroid 文件下载引擎，稳定、高效、灵活、简单易用\n\n[![Gitter][gitter_svg]][gitter_url]\n[![Download][bintray_svg]][bintray_url]\n![][file_downloader_svg]\n[![Build Status][build_status_svg]][build_status_link]\n[![][filedownloader_snapshot_svg]](https://oss.sonatype.org/content/repositories/snapshots/com/liulishuo/filedownloader/)\n\n\n> [README DOC](https://github.com/lingochamp/FileDownloader/blob/master/README.md)\n\n---\n\n#### 版本迭代日志: [Change Log](https://github.com/lingochamp/FileDownloader/blob/master/CHANGELOG.md)\n\n#### 英文文档: [Wiki](https://github.com/lingochamp/FileDownloader/wiki)、[优化建议](https://github.com/lingochamp/FileDownloader/wiki/Optimize-Tutorial)\n\n---\n\n### FileDownloader2 \n\n现在, [FileDownloader2-OkDownload](https://github.com/lingochamp/okdownload) 已经正式发布, okdownload继承了所有FileDownloader的优点，甚至做了更多的优化以及更多的特性。\n\n由于FileDownloader的单元测试覆盖太低，因此所有的进一步的需求以及提高都将会在okdownload上进行实现而非FileDownloader，而FileDownloader本身将只会关注于修复Bug。\n\n---\n\n### 特点\n\n- 简单易用\n- 单任务多线程/多连接/分块下载(并支持通过`ConnectionCountAdapter`定制)\n- 高并发\n- 灵活\n- 可选择性支持: 独立/非独立进程\n- 自动断点续传\n\n#### 需要注意\n\n- 当下载的文件大小可能大于1.99GB(2^31-1`=2_147_483_647 = 1.99GB`)的时候, 请使用`FileDownloadLargeFileListener`而不是`FileDownloadListener`(同理使用`getLargeFileSofarBytes()`与`getLargeFileTotalBytes()`)\n- 暂停: paused, 恢复: 直接调用start，默认就是断点续传\n- 引擎默认会打开避免掉帧的处理(使得在有些情况下回调(FileDownloadListener)不至于太频繁导致ui线程被ddos), 如果你希望关闭这个功能（关闭以后，所有回调会与0.1.9之前的版本一样，所有的回调会立马抛一个消息ui线程(Handler)）\n- 如果没有特殊需要，直接通过配置`filedownloader.properties`将`process.non-separate`置为`true`，可以有效减少每次回调IPC带来的I/O。\n\n---\n\n## Android 系统适配\n\n### 适配 Android 8.0\n\n从 Android 8.0 开发，后台服务的限制增强了，可以参考[这里](https://developer.android.com/about/versions/oreo/background)了解更多信息。\n因此，自 FileDownloader 1.7.6 版本开始， Android 8.0 及之后的系统上，如果在后台启动下载服务，这个服务将会是一个前台服务，同时你会看到一个标题为 \"FileDownloader\" 的通知。\n你可以参考[这里](https://github.com/lingochamp/FileDownloader/wiki/Compatibility-of-Android-O-Servic)去自定义通知的内容。\n\n### 适配 Android 9.0\n\n从 Android 9.0 (API level 28) 开始，明文请求默认被禁止，你可以在[这里](https://stackoverflow.com/questions/45940861/android-8-cleartext-http-traffic-not-permitted)了解详细信息。\nFileDownloader 1.7.6 已经在 demo 中处理了此问题。\n\n根据[迁移笔记](https://developer.android.com/about/versions/pie/android-9.0-migration#tya)，`FOREGROUND_SERVICE` 这个权限已经在 1.7.6 版本添加到 library 的 manifest 里面了。\n\n---\n\n## 欢迎提交 Pull requests\n\n- 尽量多的英文注解。\n- 每个提交尽量的细而精准。\n- Commit message 遵循: [AngularJS's commit message convention](https://github.com/angular/angular.js/blob/master/CONTRIBUTING.md#-git-commit-guidelines)。\n\n---\n\n## I. 效果\n\n![][single_demo_gif]\n![][chunked_demo_gif]\n![][serial_tasks_demo_gif]\n![][parallel_tasks_demo_gif]\n![][tasks_manager_demo_gif]\n![][mix_tasks_demo_gif]\n![][avoid_drop_frames_1_gif]\n![][avoid_drop_frames_2_gif]\n\n\n## II. 使用\n\n在项目中引用:\n\n```groovy\nimplementation 'com.liulishuo.filedownloader:library:1.7.7'\n```\n\n> 如果是eclipse引入jar包参考: [这里](https://github.com/lingochamp/FileDownloader/issues/212#issuecomment-232240415)\n\n如果需要引入snapshot版本，请添加sonatype的仓库:\n\n```groovy\nrepositories {\n    maven { url \"https://oss.sonatype.org/content/repositories/snapshots/\" }\n}\n```\n\n#### 全局初始化\n\n如果你需要注册你的定制组件，你需要在`Application#onCreate`中调用`FileDownloader.setupOnApplicationOnCreate(application):InitCustomMaker`, 否则你只需要在使用FileDownloader之前的任意时候调用`FileDownloader.setup(Context)`即可。\n\n这些初始化方法都十分的简单，不会启动下载服务，一般都是在10ms内完成。\n\n#### 启动单任务下载\n\n```java\nFileDownloader.getImpl().create(url)\n        .setPath(path)\n        .setListener(new FileDownloadListener() {\n            @Override\n            protected void pending(BaseDownloadTask task, int soFarBytes, int totalBytes) {\n            }\n\n            @Override\n            protected void connected(BaseDownloadTask task, String etag, boolean isContinue, int soFarBytes, int totalBytes) {\n            }\n\n            @Override\n            protected void progress(BaseDownloadTask task, int soFarBytes, int totalBytes) {\n            }\n\n            @Override\n            protected void blockComplete(BaseDownloadTask task) {\n            }\n\n            @Override\n            protected void retry(final BaseDownloadTask task, final Throwable ex, final int retryingTimes, final int soFarBytes) {\n            }\n\n            @Override\n            protected void completed(BaseDownloadTask task) {\n            }\n\n            @Override\n            protected void paused(BaseDownloadTask task, int soFarBytes, int totalBytes) {\n            }\n\n            @Override\n            protected void error(BaseDownloadTask task, Throwable e) {\n            }\n\n            @Override\n            protected void warn(BaseDownloadTask task) {\n            }\n        }).start();\n```\n\n#### 启动多任务下载\n\n```java\nfinal FileDownloadListener queueTarget = new FileDownloadListener() {\n    @Override\n    protected void pending(BaseDownloadTask task, int soFarBytes, int totalBytes) {\n    }\n\n    @Override\n    protected void connected(BaseDownloadTask task, String etag, boolean isContinue, int soFarBytes, int totalBytes) {\n    }\n\n    @Override\n    protected void progress(BaseDownloadTask task, int soFarBytes, int totalBytes) {\n    }\n\n    @Override\n    protected void blockComplete(BaseDownloadTask task) {\n    }\n\n    @Override\n    protected void retry(final BaseDownloadTask task, final Throwable ex, final int retryingTimes, final int soFarBytes) {\n    }\n\n    @Override\n    protected void completed(BaseDownloadTask task) {\n    }\n\n    @Override\n    protected void paused(BaseDownloadTask task, int soFarBytes, int totalBytes) {\n    }\n\n    @Override\n    protected void error(BaseDownloadTask task, Throwable e) {\n    }\n\n    @Override\n    protected void warn(BaseDownloadTask task) {\n    }\n};\n\n// 第一种方式 :\n\n//for (String url : URLS) {\n//    FileDownloader.getImpl().create(url)\n//            .setCallbackProgressTimes(0) // 由于是队列任务, 这里是我们假设了现在不需要每个任务都回调`FileDownloadListener#progress`, 我们只关系每个任务是否完成, 所以这里这样设置可以很有效的减少ipc.\n//            .setListener(queueTarget)\n//            .asInQueueTask()\n//            .enqueue();\n//}\n\n//if(serial){\n    // 串行执行该队列\n//    FileDownloader.getImpl().start(queueTarget, true);\n// }\n\n// if(parallel){\n    // 并行执行该队列\n//    FileDownloader.getImpl().start(queueTarget, false);\n//}\n\n// 第二种方式:\n\nfinal FileDownloadQueueSet queueSet = new FileDownloadQueueSet(downloadListener);\n\nfinal List<BaseDownloadTask> tasks = new ArrayList<>();\nfor (int i = 0; i < count; i++) {\n     tasks.add(FileDownloader.getImpl().create(Constant.URLS[i]).setTag(i + 1));\n}\n\nqueueSet.disableCallbackProgressTimes(); // 由于是队列任务, 这里是我们假设了现在不需要每个任务都回调`FileDownloadListener#progress`, 我们只关系每个任务是否完成, 所以这里这样设置可以很有效的减少ipc.\n\n// 所有任务在下载失败的时候都自动重试一次\nqueueSet.setAutoRetryTimes(1);\n\nif (serial) {\n  // 串行执行该任务队列\n     queueSet.downloadSequentially(tasks);\n     // 如果你的任务不是一个List，可以考虑使用下面的方式，可读性更强\n//      queueSet.downloadSequentially(\n//              FileDownloader.getImpl().create(url).setPath(...),\n//              FileDownloader.getImpl().create(url).addHeader(...,...),\n//              FileDownloader.getImpl().create(url).setPath(...)\n//      );\n}\n\nif (parallel) {\n  // 并行执行该任务队列\n   queueSet.downloadTogether(tasks);\n   // 如果你的任务不是一个List，可以考虑使用下面的方式，可读性更强\n//    queueSet.downloadTogether(\n//            FileDownloader.getImpl().create(url).setPath(...),\n//            FileDownloader.getImpl().create(url).setPath(...),\n//            FileDownloader.getImpl().create(url).setSyncCallback(true)\n//    );\n}\n\n// 最后你需要主动调用start方法来启动该Queue\nqueueSet.start()\n\n// 串行任务动态管理也可以使用FileDownloadSerialQueue。\n```\n\n#### 全局接口说明(`FileDownloader`)\n\n> 所有的暂停，就是停止，会释放所有资源并且停到所有相关线程，下次启动的时候默认会断点续传\n\n| 方法名 | 备注\n| --- | ---\n| setup(Context) | 如果不需要注册定制组件，就使用该方法在使用下载引擎前调用，该方法只会缓存Context\n| setupOnApplicationOnCreate(application):InitCustomMaker | 如果需要注册定制组件，就在Application#onCreate中调用该方法来注册定制组件以及初始化下载引擎，该方法不会启动下载服务\n| create(url:String) | 创建一个下载任务\n| start(listener:FileDownloadListener, isSerial:boolean) | 启动是相同监听器的任务，串行/并行启动\n| pause(listener:FileDownloadListener) | 暂停启动相同监听器的任务\n| pauseAll(void) | 暂停所有任务\n| pause(downloadId) | 暂停downloadId的任务\n| clear(downloadId, targetFilePath) | 强制清理ID为downloadId的任务在filedownloader中的数据\n| getSoFar(downloadId) | 获得下载Id为downloadId的soFarBytes\n| getTotal(downloadId) | 获得下载Id为downloadId的totalBytes\n| bindService(void) | 主动启动下载进程(可事先调用该方法(可以不调用)，保证第一次下载的时候没有启动进程的速度消耗)\n| unBindService(void) | 主动关停下载进程\n| unBindServiceIfIdle(void) | 如果目前下载进程没有任务正在执行，则关停下载进程\n| isServiceConnected(void) | 是否已经启动并且连接上下载进程(可参考任务管理demo中的使用)\n| getStatusIgnoreCompleted(downloadId) | 获取不包含已完成状态的下载状态(如果任务已经下载完成，将收到`INVALID`)\n| getStatus(id:int, path:String) | 获取下载状态\n| getStatus(url:String, path:String) | 获取下载状态\n| setGlobalPost2UIInterval(intervalMillisecond:int) | 为了避免掉帧，这里是设置了最多每interval毫秒抛一个消息到ui线程(使用Handler)，防止由于回调的过于频繁导致ui线程被ddos导致掉帧。 默认值: 10ms. 如果设置小于0，将会失效，也就是说每个回调都直接抛一个消息到ui线程\n| setGlobalHandleSubPackageSize(packageSize:int) | 为了避免掉帧, 如果上面的方法设置的间隔是一个小于0的数，这个packageSize将不会生效。packageSize这个值是为了避免在ui线程中一次处理过多回调，结合上面的间隔，就是每个interval毫秒间隔抛一个消息到ui线程，而每个消息在ui线程中处理packageSize个回调。默认值: 5\n| enableAvoidDropFrame(void) | 开启 避免掉帧处理。就是将抛消息到ui线程的间隔设为默认值10ms, 很明显会影响的是回调不会立马通知到监听器(FileDownloadListener)中，默认值是: 最多10ms处理5个回调到监听器中\n| disableAvoidDropFrame(void) | 关闭 避免掉帧处理。就是将抛消息到ui线程的间隔设置-1(无效值)，这个就是让每个回调都会抛一个消息ui线程中，可能引起掉帧\n| isEnabledAvoidDropFrame(void) | 是否开启了 避免掉帧处理。默认是开启的\n| startForeground(id:int, notification:Notification) | 设置FileDownloadService为前台模式，保证用户从最近应用列表移除应用以后下载服务不会被杀\n| stopForeground(removeNotification:boolean) | 取消FileDownloadService的前台模式\n| setTaskCompleted(url:String, path:String, totalBytes:long) | 用于告诉FileDownloader引擎，以指定Url与Path的任务已经通过其他方式(非FileDownloader)下载完成\n| setTaskCompleted(taskAtomList:List<FileDownloadTaskAtom>) | 用于告诉FileDownloader引擎，指定的一系列的任务都已经通过其他方式(非FileDownloader)下载完成\n| setMaxNetworkThreadCount(int) | 设置最大并行下载的数目(网络下载线程数), [1,12]\n| clearAllTaskData() | 清空`filedownloader`数据库中的所有数据\n\n#### 定制化组件接口说明(`InitCustomMaker`)\n\n| 方法名 | 需实现接口 | 已有组件 | 默认组件 | 说明\n| --- | --- | --- | --- | ---\n| database | FileDownloadDatabase | RemitDatabase、SqliteDatabaseImpl、NoDatabaseImpl | RemitDatabase | 传入定制化数据库组件，用于存储用于断点续传的数据\n| connection | FileDownloadConnection | FileDownloadUrlConnection | FileDownloadUrlConnection | 传入定制化的网络连接组件，用于下载时建立网络连接\n| outputStreamCreator | FileDownloadOutputStream | FileDownloadRandomAccessFile | FileDownloadRandomAccessFile | 传入输出流组件，用于下载时写文件使用\n| maxNetworkThreadCount | - | - | 3 | 传入创建下载引擎时，指定可用的下载线程个数\n| ConnectionCountAdapter | ConnectionCountAdapter | DefaultConnectionCountAdapter | DefaultConnectionCountAdapter | 根据任务指定其线程数\n| IdGenerator | IdGenerator | DefaultIdGenerator | DefaultIdGenerator | 自定义任务Id生成器\n\n> - 如果你希望Okhttp作为你的网络连接组件，可以使用[这个库](https://github.com/Jacksgong/filedownloader-okhttp3-connection)。\n> - 如果你不希望FileDownloader用到任何的数据库(是用于存储任务的断点续成信息的)，只需要使用[NoDatabaseImpl.java](https://github.com/lingochamp/FileDownloader/blob/master/library/src/main/java/com/liulishuo/filedownloader/services/NoDatabaseImpl.java)即可。\n\n#### Task接口说明\n\n| 方法名 | 备注\n| --- | ---\n| setPath(path:String) | 下载文件的存储绝对路径\n| setPath(path:String, pathAsDirectory:boolean) | 如果`pathAsDirectory`是`true`,`path`就是存储下载文件的文件目录(而不是路径)，此时默认情况下文件名`filename`将会默认从`response#header`中的`contentDisposition`中获得\n| setListener(listener:FileDownloadListener) | 设置监听，可以以相同监听组成队列\n| setCallbackProgressTimes(times:int) | 设置整个下载过程中`FileDownloadListener#progress`最大回调次数\n| setCallbackProgressIgnored() | 忽略所有的`FileDownloadListener#progress`的回调\n| setCallbackProgressMinInterval(minIntervalMillis:int) | 设置每个`FileDownloadListener#progress`之间回调间隔(ms)\n| setTag(tag:Object) | 内部不会使用，在回调的时候用户自己使用\n| setTag(key:int, tag:Object) | 用于存储任意的变量方便回调中使用，以key作为索引\n| setForceReDownload(isForceReDownload:boolean) | 强制重新下载，将会忽略检测文件是否健在\n| setFinishListener(listener:FinishListener) | 结束监听，仅包含结束(over(void))的监听\n| setAutoRetryTimes(autoRetryTimes:int) | 当请求或下载或写文件过程中存在错误时，自动重试次数，默认为0次\n| setSyncCallback(syncCallback:boolean)  | 如果设为true, 所有FileDownloadListener中的回调都会直接在下载线程中回调而不抛到ui线程, 默认为false\n| addHeader(name:String, value:String) | 添加自定义的请求头参数，需要注意的是内部为了断点续传，在判断断点续传有效时会自动添加上(`If-Match`与`Range`参数)，请勿重复添加导致400或其他错误\n| addHeader(line:String) | 添加自定义的请求头参数，需要注意的是内部为了断点续传，在判断断点续传有效时会自动添加上(`If-Match`与`Range`参数)，请勿重复添加导致400或其他错误\n| setMinIntervalUpdateSpeed(minIntervalUpdateSpeedMs:int) | 设置下载中刷新下载速度的最小间隔\n| removeAllHeaders(name:String) | 删除由自定义添加上去请求参数为`{name}`的所有键对\n| setWifiRequired(isWifiRequired:boolean) | 设置任务是否只允许在Wifi网络环境下进行下载。 默认值 `false`\n| asInQueueTask(void):InQueueTask | 申明该任务将会是队列任务中的一个任务，并且转化为`InQueueTask`，之后可以调用`InQueueTask#enqueue`将该任务入队以便于接下来启动队列任务时，可以将该任务收编到队列中\n| start(void) | 启动孤立的下载任务\n| pause(void) | 暂停下载任务(也可以理解为停止下载，但是在start的时候默认会断点续传)\n| getId(void):int | 获取唯一Id(内部通过url与path生成)\n| getUrl(void):String | 获取下载连接\n| getCallbackProgressTimes(void):int | 获得progress最大回调次数\n| getCallbackProgressMinInterval(void):int | 获得每个progress之间的回调间隔(ms)\n| getPath(void):String | 获取文件路径 或 文件目录\n| isPathAsDirectory | 判断`getPath()`返回的路径是文件存储目录(`directory`)，还是文件存储路径(`directory/filename`)\n| getTargetFilePath | 获取目标文件的存储路径\n| getListener(void):FileDownloadListener | 获取监听器\n| getSoFarBytes(void):int | 获取已经下载的字节数\n| getTotalBytes(void):int | 获取下载文件总大小\n| getStatus(void):int | 获取当前的状态\n| isForceReDownload(void):boolean | 是否强制重新下载\n| getEx(void):Throwable | 获取下载过程抛出的Throwable\n| isReusedOldFile(void):boolean | 判断是否是直接使用了旧文件(检测是有效文件)，没有启动下载\n| getTag(void):Object | 获取用户setTag进来的Object\n| getTag(key:int):Object | 根据key获取存储在task中的变量\n| isContinue(void):boolean | 是否成功断点续传\n| getEtag(void):String | 获取当前下载获取到的ETag\n| getAutoRetryTimes(void):int | 自动重试次数\n| getRetryingTimes(void):int | 当前重试次数。将要开始重试的时候，会将接下来是第几次\n| isSyncCallback(void):boolean | 是否是设置了所有FileDownloadListener中的回调都直接在下载线程直接回调而不抛到ui线程\n| getSpeed():int | 获取任务的下载速度, 下载过程中为实时速度，下载结束状态为平均速度\n| isUsing():boolean | 判断当前的Task对象是否在引擎中启动过\n| isWifiRequired():boolean | 获取当前任务是否被设置过只允许在Wifi网络环境下下载\n\n#### 监听器(`FileDownloadListener`)说明\n\n##### 一般的下载回调流程:\n\n```\npending -> started -> connected -> (progress <->progress) -> blockComplete -> completed\n```\n\n##### 可能会遇到以下回调而直接终止整个下载过程:\n\n```\npaused / completed / error / warn\n```\n\n##### 如果检测存在已经下载完成的文件(可以通过`isReusedOldFile`进行决策是否是该情况)(也可以通过`setForceReDownload(true)`来避免该情况):\n\n```\nblockComplete -> completed\n```\n\n##### 方法说明\n\n| 回调方法 | 备注 | 带回数据\n| --- | --- | ---\n| pending | 等待，已经进入下载队列 | 数据库中的soFarBytes与totalBytes\n| started | 结束了pending，并且开始当前任务的Runnable | -\n| connected | 已经连接上 | ETag, 是否断点续传, soFarBytes, totalBytes\n| progress | 下载进度回调 | soFarBytes\n| blockComplete | 在完成前同步调用该方法，此时已经下载完成 | -\n| retry | 重试之前把将要重试是第几次回调回来 | 之所以重试遇到Throwable, 将要重试是第几次, soFarBytes\n| completed | 完成整个下载过程 | -\n| paused | 暂停下载 | soFarBytes\n| error | 下载出现错误 | 抛出的Throwable\n| warn | 在下载队列中(正在等待/正在下载)已经存在相同下载连接与相同存储路径的任务 | -\n\n\n![][file_download_listener_callback_flow_png]\n\n##### 由于`FileDownloadListener`中的方法回调过快，导致掉帧?\n\n> 你有两种方法可以解决这个问题\n\n1. `FileDownloader#enableAvoidDropFrame`, 默认 就是开启的\n2. `BaseDownloadTask#setSyncCallback`, 默认是false, 如果设置为true，所有的回调都会在下载线程直接同步调用而不会抛到ui线程。\n\n#### `FileDownloadMonitor`\n\n> 你可以添加一个全局监听器来进行打点或者是调试\n\n| 方法名 | 备注\n| --- | ---\n| setGlobalMonitor(monitor:IMonitor) | 设置与替换一个全局监听器到下载引擎中\n| releaseGlobalMonitor(void) | 释放已经设置到下载引擎中的全局监听器\n| getMonitor(void) | 获取已经设置到下载引擎中的全局监听器\n\n\n##### `FileDownloadMonitor.IMonitor`\n\n> 监听器接口类\n\n|  接口 | 备注\n| --- | ---\n| onRequestStart(count:int, serial:boolean, lis:FileDownloadListener) | 将会在启动队列任务是回调这个方法\n| onRequestStart(task:BaseDownloadTask) | 将会在启动单一任务时回调这个方法\n| onTaskBegin(task:BaseDownloadTask) | 将会在内部接收并开始task的时候回调这个方法(会在`pending`回调之前)\n| onTaskStarted(task:BaseDownloadTask) | 将会在task结束pending开始task的runnable的时候回调该方法\n| onTaskOver(task:BaseDownloadTask) | 将会在task走完所有生命周期是回调这个方法\n\n#### `FileDownloadUtils`\n\n| 方法名 | 备注\n| --- | ---\n| setDefaultSaveRootPath(path:String) | 在整个引擎中没有设置路径时`BaseDownloadTask#setPath`这个路径将会作为它的Root path\n| getTempPath | 获取用于存储还未下载完成文件的临时存储路径: `filename.temp`\n| isFilenameConverted(context:Context) | 判断是否所有数据库中下载中的任务的文件名都已经从`filename`(在旧架构中)转为`filename.temp`\n\n#### `FileDownloadNotificationHelper`\n\n> 如何快速集成Notification呢? 建议参考[NotificationMinSetActivity](https://github.com/lingochamp/FileDownloader/blob/master/demo/src/main/java/com/liulishuo/filedownloader/demo/NotificationMinSetActivity.java)、[NotificationSampleActivity](https://github.com/lingochamp/FileDownloader/blob/master/demo/src/main/java/com/liulishuo/filedownloader/demo/NotificationSampleActivity.java)。\n\n#### `filedownloader.properties`\n\n> 如果你需要定制化FileDownloader，可以在你的项目模块的`assets` 目录下添加 'filedownloader.properties' 文件(如 `/demo/src/main/assets/filedownloader.properties`)，然后添加以下可选相关配置。\n\n> 格式: `keyword=value`\n\n| 关键字 | 描述 | 默认值\n| --- | --- | ---\n| http.lenient | 如果你遇到了: 'can't know the size of the download file, and its Transfer-Encoding is not Chunked either', 但是你想要忽略类似的返回头不规范的错误，直接将该关键字参数设置为`true`即可，我们将会将其作为`chunck`进行处理 | false\n| process.non-separate | FileDownloadService 默认是运行在独立进程':filedownloader'上的, 如果你想要FileDownloadService共享并运行在主进程上, 将该关键字参数设置为`true`，可以有效减少IPC产生的I/O | false\n| download.min-progress-step | 最小缓冲大小，用于判定是否是时候将缓冲区中进度同步到数据库，以及是否是时候要确保下缓存区的数据都已经写文件。值越小，更新会越频繁，下载速度会越慢，但是应对进程被无法预料的情况杀死时会更加安全 | 65536\n| download.min-progress-time | 最小缓冲时间，用于判定是否是时候将缓冲区中进度同步到数据库，以及是否是时候要确保下缓存区的数据都已经写文件。值越小，更新会越频繁，下载速度会越慢，但是应对进程被无法预料的情况杀死时会更加安全 | 2000\n| download.max-network-thread-count | 用于同时下载的最大网络线程数, 区间[1, 12] | 3\n| file.non-pre-allocation | 是否不需要在开始下载的时候，预申请整个文件的大小(`content-length`) | false\n| broadcast.completed | 是否需要在任务下载完成后发送一个完成的广播 | false\n\n> 如果你使用`broadcast.completed`并且接收任务完成的广播,你需要注册Action为`filedownloader.intent.action.completed`的广播并且使用`FileDownloadBroadcastHandler`来处理接收到的`Intent`。\n\nIII. 异常处理\n\n> 所有的异常，都将在 `FileDownloadListener#error(BaseDownloadTask, Throwable)` 中获知。\n\n| Exception | 原因\n| --- | ---\n| `FileDownloadHttpException`| 在发出请求以后，response-code不是200(HTTP_OK)，也不是206(HTTP_PARTIAL)的情况下会抛出该异常; 在这个异常对象会带上 response-code、response-header、request-header。\n| `FileDownloadGiveUpRetryException` | 在请求返回的 response-header 中没有带有文件大小(content-length)，并且不是流媒体(transfer-encoding)的情况下会抛出该异常；出现这个异常，将会忽略所有重试的机会(`BaseDownloadTask#setAutoRetryTimes`). 你可以通过在 `filedownloader.properties`中添加 `http.lenient=true` 来忽略这个异常，并且在该情况下，直接作为流媒体进行下载。\n| `FileDownloadOutOfSpaceException` | 当将要下载的文件大小大于剩余磁盘大小时，会抛出这个异常。\n| 其他 | 程序错误。\n| `FileDownloadNetworkPolicyException` | 设置了`BaseDownloadTask#setWifiRequired(true)`，在下载过程中，一旦发现网络情况转为非Wifi环境，便会抛回这个异常\n| `PathConflictException` | 当有一个正在下载的任务，它的存储路径与当前任务的存储路径完全一致，为了避免多个任务对同一个文件进行写入，当前任务便会抛回这个异常\n\n\n\n## III. 低内存情况\n\n### 非下载进程(一般是UI进程):\n\n> 这边的数据并不多，只是一些队列数据，用不了多少内存。\n\n#### [前台进程](http://developer.android.com/intl/zh-cn/guide/components/processes-and-threads.html)数据被回收:\n\n如果在前台的时候这个数据都被回收了, 你的应用应该也挂了。极低概率事件。\n\n#### [后台进程](http://developer.android.com/intl/zh-cn/guide/components/processes-and-threads.html)数据被回收:\n\n一般事件, 如果是你的下载是UI进程启动的，如果你的UI进程处于`后台进程`(可以理解为应用被退到后台)状态，在内存不足的情况下会被回收(回收优先级高于`服务进程`)，此时分两种情况:\n\n1. 是串行队列任务，在回收掉UI进程内存以后，下载进程会继续下载完已经pending到下载进程的那个任务，而还未pending到下载进程的任务会中断下载(由于任务驱动线性执行的是在UI进程); 有损体验: 下次进入应用重启启动整个队列，会继续上次的下载。\n\n2. 是并行队列任务，在回收掉UI进程内存以后，下载进程会继续下载所有任务(所有已经pending到下载进程的任务，由于这里的pending速度是很快的，因此几乎是点击并行下载，所有任务在很短的时间内都已经pending到下载进程了)，而UI进程由于被回收，将不会收到所有的监听; 有损体验: 下次进入应用重新启动整个队列，就会和正常的下载启动一致，收到所有情况的监听。\n\n### 下载进程:\n\n> 对内存有一定的占用，但是并不多，每次启动进程会根据数据的有效性进行清理冗余数据，被回收是低概率事件\n\n由于下载不断有不同的buffer占用内存，但是由于在下载时，是活跃的`服务进程`，因此被回收是低概率事件(会先回收完所有`空进程`、`后台进程`(后台应用)以后，如果内存还不够，才会回收该进程)。\n\n即使被回收，也不会有任何问题。由于我们使用的是`START_STICKY`(如果不希望被重启可主动调用`FileDownloader#unBindService`/`FileDownloader#unBindServiceIfIdle`)，因此在内存足够的时候，下载进程会尝试重启(系统调度)，非下载进程(一般是UI进程) 接收到下载进程的连接，会继续下载与继续接收回调，下载进程也会断点续传没有下载完的所有任务(无论并行与串行)，不会影响体验。\n\n## IV. LICENSE\n\n```\nCopyright (c) 2015 LingoChamp Inc.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n   http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n```\n\n[gitter_url]: https://gitter.im/lingochamp/FileDownloader?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge\n[gitter_svg]: https://badges.gitter.im/lingochamp/FileDownloader.svg\n[license_2_svg]: https://img.shields.io/hexpm/l/plug.svg\n[android_platform_svg]: https://img.shields.io/badge/Platform-Android-brightgreen.svg\n[file_downloader_svg]: https://img.shields.io/badge/Android-FileDownloader-orange.svg\n[mix_tasks_demo_gif]: https://github.com/lingochamp/FileDownloader/raw/master/art/mix_tasks_demo.gif\n[parallel_tasks_demo_gif]: https://github.com/lingochamp/FileDownloader/raw/master/art/parallel_tasks_demo.gif\n[serial_tasks_demo_gif]: https://github.com/lingochamp/FileDownloader/raw/master/art/serial_tasks_demo.gif\n[tasks_manager_demo_gif]: https://github.com/lingochamp/FileDownloader/raw/master/art/tasks_manager_demo.gif\n[avoid_drop_frames_1_gif]: https://github.com/lingochamp/FileDownloader/raw/master/art/avoid_drop_frames1.gif\n[avoid_drop_frames_2_gif]: https://github.com/lingochamp/FileDownloader/raw/master/art/avoid_drop_frames2.gif\n[single_demo_gif]: https://github.com/lingochamp/FileDownloader/raw/master/art/single_demo.gif\n[chunked_demo_gif]: https://github.com/lingochamp/FileDownloader/raw/master/art/chunked_demo.gif\n[bintray_svg]: https://api.bintray.com/packages/jacksgong/maven/FileDownloader/images/download.svg\n[bintray_url]: https://bintray.com/jacksgong/maven/FileDownloader/_latestVersion\n[file_download_listener_callback_flow_png]: https://github.com/lingochamp/FileDownloader/raw/master/art/filedownloadlistener_callback_flow.png\n[build_status_svg]: https://travis-ci.org/lingochamp/FileDownloader.svg?branch=master\n[filedownloader_snapshot_svg]: https://img.shields.io/badge/SnapShot-1.7.8-yellow.svg\n[build_status_link]: https://travis-ci.org/lingochamp/FileDownloader\n"
  },
  {
    "path": "README.md",
    "content": "# FileDownloader\nAndroid multi-task file download engine.\n\n\n[![Download][bintray_svg]][bintray_url]\n![][file_downloader_svg]\n[![Build Status][build_status_svg]][build_status_link]\n[![][filedownloader_snapshot_svg]](https://oss.sonatype.org/content/repositories/snapshots/com/liulishuo/filedownloader/)\n\n> [中文文档](https://github.com/lingochamp/FileDownloader/blob/master/README-zh.md)\n\n## FileDownloader2\n\nNow, [FileDownloader2-OkDownload](https://github.com/lingochamp/okdownload) is released, okdownload will contain all advantage on the FileDownloader and beyond.\n\nBecause of FileDownloader unit-test coverage is very low, so all farther features and enhances will be achieved on the okdownload instead of FileDownloader, and FileDownloader will only focuses on bug fixes.\n\n## DEMO\n\n![][single_demo_gif]\n![][chunked_demo_gif]\n![][serial_tasks_demo_gif]\n![][parallel_tasks_demo_gif]\n![][tasks_manager_demo_gif]\n![][hybrid_test_demo_gif]\n![][avoid_drop_frames_1_gif]\n![][avoid_drop_frames_2_gif]\n\n## Installation\n\nFileDownloader is installed by adding the following dependency to your `build.gradle` file:\n\n```groovy\ndependencies {\n    implementation 'com.liulishuo.filedownloader:library:1.7.7'\n}\n```\n\nSnapshots of the development version are available in [Sonatype's `snapshots` repository](https://oss.sonatype.org/content/repositories/snapshots/), you can include on your gradle project through:\n\n```groovy\nrepositories {\n    maven { url \"https://oss.sonatype.org/content/repositories/snapshots/\" }\n}\n```\n\n## Open customize component\n\nFrom now on, FileDownloader support following components to be customized by yourself:\n\n| Name | Interface | Default Impl\n| --- | --- | ---\n| Connection | [FileDownloadConnection][FileDownloadConnection-java-link] | [FileDownloadUrlConnection][FileDownloadUrlConnection-java-link]\n| OutputStream | [FileDownloadOutputStream][FileDownloadOutputStream-java-link] | [FileDownloadRandomAccessFile][FileDownloadRandomAccessFile-java-link]\n| Database | [FileDownloadDatabase][FileDownloadDatabase-java-link] | [RemitDatabase][RemitDatabase-java-link]\n| ConnectionCountAdapter | [ConnectionCountAdapter][ConnectionCountAdapter-java-link] | [DefaultConnectionCountAdapter][DefaultConnectionCountAdapter-java-link]\n| IdGenerator | [IdGenerator][IdGenerator-java-link] | [DefaultIdGenerator][DefaultIdGenerator-java-link]\n| ForegroundServiceConfig | [ForegroundServiceConfig][ForegroundServiceConfig-java-link] | [ForegroundServiceConfig][ForegroundServiceConfig-java-link]\n\n> - If you want to use okhttp as your connection component, the simplest way is [this repo](https://github.com/Jacksgong/filedownloader-okhttp3-connection).\n> - If you don't want to use any database on FileDownloader(the database on FileDownloader is used for persist tasks' breakpoint info) just using [NoDatabaseImpl.java](https://github.com/lingochamp/FileDownloader/blob/master/library/src/main/java/com/liulishuo/filedownloader/services/NoDatabaseImpl.java)\n\n### How to valid it?\n\nJust create your own `DownloadMgrInitialParams.InitCustomMaker` and put those customized component to it, finally init the FileDownloader with it: [FileDownloader#init](https://github.com/lingochamp/FileDownloader/blob/master/library/src/main/java/com/liulishuo/filedownloader/FileDownloader.java#L62)\n\n## Adaptation\n\n### Adapt to Android 8.0\n\nThe restriction of background service has been tightened since Android 8.0, for more details, please refer to [here](https://developer.android.com/about/versions/oreo/background).\nSo, after Android 8.0, the download service will be a foreground service when start downloading during app is in background and you will see a notification with a title named \"FileDownloader\" start from FileDownloader 1.7.6.\nYou can refer to [here](https://github.com/lingochamp/FileDownloader/wiki/Compatibility-of-Android-O-Service) to custom the notification.\n\n### Adapt to Android 9.0\n\nStarting with Android 9.0 (API level 28), cleartext support is disabled by default, you can have a look at [here](https://stackoverflow.com/questions/45940861/android-8-cleartext-http-traffic-not-permitted) to know about more details.\nFileDownloader demo has handled this problem start with 1.7.6.\n\nAccording to the [migration notes](https://developer.android.com/about/versions/pie/android-9.0-migration#tya), the FOREGROUND_SERVICE permission has been added to the library manifest since FileDownloader 1.7.6.\n\n## Welcome PR\n\n> If you can improve the unit test for this project would be great.\n\n- Comments as much as possible.\n- Commit message format follow: [AngularJS's commit message convention](https://github.com/angular/angular.js/blob/master/CONTRIBUTING.md#-git-commit-guidelines) .\n- The change of each commit as small as possible.\n\n![][structure-img]\n![][message-system-img]\n\n## Usage\n\nBy default, the FileDownloadService runs on the separate process, if you want to run it on the main process, just configure on the [filedownloader.properties](https://github.com/lingochamp/FileDownloader/wiki/filedownloader.properties), and you can use `FileDownloadUtils.isDownloaderProcess(Context)` to check whether the FileDownloadService can run on the current process.\n\nFor more readable, Moved to [Wiki](https://github.com/lingochamp/FileDownloader/wiki).\n\n## LICENSE\n\n```\nCopyright (c) 2015 LingoChamp Inc.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n   http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n```\n\n[license_2_svg]: https://img.shields.io/hexpm/l/plug.svg\n[android_platform_svg]: https://img.shields.io/badge/Platform-Android-brightgreen.svg\n[file_downloader_svg]: https://img.shields.io/badge/Android-FileDownloader-orange.svg\n[structure-img]: https://github.com/lingochamp/FileDownloader/raw/master/art/structure.png\n[message-system-img]: https://github.com/lingochamp/FileDownloader/raw/master/art/message-system.png\n[hybrid_test_demo_gif]: https://github.com/lingochamp/FileDownloader/raw/master/art/hybrid_test_demo.gif\n[parallel_tasks_demo_gif]: https://github.com/lingochamp/FileDownloader/raw/master/art/parallel_tasks_demo.gif\n[serial_tasks_demo_gif]: https://github.com/lingochamp/FileDownloader/raw/master/art/serial_tasks_demo.gif\n[tasks_manager_demo_gif]: https://github.com/lingochamp/FileDownloader/raw/master/art/tasks_manager_demo.gif\n[avoid_drop_frames_1_gif]: https://github.com/lingochamp/FileDownloader/raw/master/art/avoid_drop_frames1.gif\n[avoid_drop_frames_2_gif]: https://github.com/lingochamp/FileDownloader/raw/master/art/avoid_drop_frames2.gif\n[single_demo_gif]: https://github.com/lingochamp/FileDownloader/raw/master/art/single_demo.gif\n[chunked_demo_gif]: https://github.com/lingochamp/FileDownloader/raw/master/art/chunked_demo.gif\n[bintray_svg]: https://api.bintray.com/packages/jacksgong/maven/FileDownloader/images/download.svg\n[bintray_url]: https://bintray.com/jacksgong/maven/FileDownloader/_latestVersion\n[file_download_listener_callback_flow_png]: https://github.com/lingochamp/FileDownloader/raw/master/art/filedownloadlistener_callback_flow.png\n[build_status_svg]: https://travis-ci.org/lingochamp/FileDownloader.svg?branch=master\n[filedownloader_snapshot_svg]: https://img.shields.io/badge/SnapShot-1.7.8-yellow.svg\n[build_status_link]: https://travis-ci.org/lingochamp/FileDownloader\n[FileDownloadConnection-java-link]: https://github.com/lingochamp/FileDownloader/blob/master/library/src/main/java/com/liulishuo/filedownloader/connection/FileDownloadConnection.java\n[FileDownloadUrlConnection-java-link]: https://github.com/lingochamp/FileDownloader/blob/master/library/src/main/java/com/liulishuo/filedownloader/connection/FileDownloadUrlConnection.java\n[FileDownloadDatabase-java-link]: https://github.com/lingochamp/FileDownloader/blob/master/library/src/main/java/com/liulishuo/filedownloader/database/RemitDatabase.java\n[RemitDatabase-java-link]: https://github.com/lingochamp/FileDownloader/blob/master/library/src/main/java/com/liulishuo/filedownloader/database/RemitDatabase.java\n[FileDownloadOutputStream-java-link]: https://github.com/lingochamp/FileDownloader/blob/master/library/src/main/java/com/liulishuo/filedownloader/stream/FileDownloadOutputStream.java\n[FileDownloadRandomAccessFile-java-link]: https://github.com/lingochamp/FileDownloader/blob/master/library/src/main/java/com/liulishuo/filedownloader/stream/FileDownloadRandomAccessFile.java\n[ConnectionCountAdapter-java-link]: https://github.com/lingochamp/FileDownloader/blob/master/library/src/main/java/com/liulishuo/filedownloader/util/FileDownloadHelper.java#L100\n[DefaultConnectionCountAdapter-java-link]: https://github.com/lingochamp/FileDownloader/blob/master/library/src/main/java/com/liulishuo/filedownloader/connection/DefaultConnectionCountAdapter.java\n[IdGenerator-java-link]: https://github.com/lingochamp/FileDownloader/blob/master/library/src/main/java/com/liulishuo/filedownloader/util/FileDownloadHelper.java#L55\n[DefaultIdGenerator-java-link]: https://github.com/lingochamp/FileDownloader/blob/master/library/src/main/java/com/liulishuo/filedownloader/services/DefaultIdGenerator.java\n[ForegroundServiceConfig-java-link]:https://github.com/lingochamp/FileDownloader/blob/master/library/src/main/java/com/liulishuo/filedownloader/services/ForegroundServiceConfig.java\n"
  },
  {
    "path": "build.gradle",
    "content": "buildscript {\n    repositories {\n        jcenter()\n        google()\n    }\n    dependencies {\n        classpath 'com.android.tools.build:gradle:3.3.1'\n        classpath 'com.jfrog.bintray.gradle:gradle-bintray-plugin:1.7.3'\n    }\n}\n\nallprojects {\n    repositories {\n        jcenter()\n        google()\n    }\n}\n\nsubprojects {\n    group = GROUP\n    version = VERSION_NAME\n}\n\ntask clean(type: Delete) {\n    delete rootProject.buildDir\n}\n"
  },
  {
    "path": "checkstyle.xml",
    "content": "<?xml version=\"1.0\"?>\n<!DOCTYPE module PUBLIC\n    \"-//Puppy Crawl//DTD Check Configuration 1.3//EN\"\n    \"http://checkstyle.sourceforge.net/dtds/configuration_1_3.dtd\">\n\n<module name=\"Checker\">\n\n    <module name=\"SuppressWarningsFilter\" />\n\n    <!--module name=\"NewlineAtEndOfFile\"/-->\n    <module name=\"FileLength\"/>\n    <module name=\"FileTabCharacter\"/>\n\n    <!-- Trailing spaces -->\n    <module name=\"RegexpSingleline\">\n        <property name=\"format\" value=\"\\s+$\"/>\n        <property name=\"message\" value=\"Line has trailing spaces.\"/>\n    </module>\n\n    <!-- Space after 'for' and 'if' -->\n    <module name=\"RegexpSingleline\">\n        <property name=\"format\" value=\"^\\s*(for|if)[^ ]\\(\"/>\n        <property name=\"message\" value=\"Space needed before opening parenthesis.\"/>\n    </module>\n\n    <!-- For each spacing -->\n    <module name=\"RegexpSingleline\">\n        <property name=\"format\" value=\"^\\s*for \\(.*?([^ ]:|:[^ ])\"/>\n        <property name=\"message\" value=\"Space needed around ':' character.\"/>\n    </module>\n\n    <module name=\"TreeWalker\">\n        <!--<property name=\"cacheFile\" value=\"${checkstyle.cache.file}\"/>-->\n\n        <!-- Checks for Javadoc comments.                     -->\n        <!-- See http://checkstyle.sf.net/config_javadoc.html -->\n        <!--module name=\"JavadocMethod\"/-->\n        <!--module name=\"JavadocType\"/-->\n        <!--module name=\"JavadocVariable\"/-->\n        <!--module name=\"JavadocStyle\"/-->\n\n        <module name=\"SuppressWarningsHolder\" />\n\n        <!-- Checks for Naming Conventions.                  -->\n        <!-- See http://checkstyle.sf.net/config_naming.html -->\n        <module name=\"ConstantName\"/>\n        <module name=\"LocalFinalVariableName\"/>\n        <module name=\"LocalVariableName\"/>\n        <module name=\"MemberName\"/>\n        <module name=\"MethodName\">\n            <property name=\"format\" value=\"^[a-z][a-zA-Z0-9_]*$\"/>\n        </module>\n        <module name=\"PackageName\"/>\n        <module name=\"ParameterName\"/>\n        <!--<module name=\"StaticVariableName\"/>-->\n        <module name=\"TypeName\"/>\n\n\n        <!-- Checks for imports                              -->\n        <!-- See http://checkstyle.sf.net/config_import.html -->\n        <module name=\"AvoidStarImport\"/>\n        <module name=\"IllegalImport\"/>\n        <module name=\"RedundantImport\"/>\n        <module name=\"UnusedImports\">\n            <property name=\"processJavadoc\" value=\"true\"/>\n        </module>\n\n\n        <!-- Checks for Size Violations.                    -->\n        <!-- See http://checkstyle.sf.net/config_sizes.html -->\n        <module name=\"LineLength\">\n            <property name=\"max\" value=\"100\"/>\n        </module>\n        <module name=\"MethodLength\">\n            <property name=\"max\" value=\"200\"/>\n        </module>\n        <!--<module name=\"ParameterNumber\">-->\n            <!--<property name=\"max\" value=\"10\"/>-->\n        <!--</module>-->\n\n\n        <!-- Checks for whitespace                               -->\n        <!-- See http://checkstyle.sf.net/config_whitespace.html -->\n        <module name=\"GenericWhitespace\"/>\n        <!--<module name=\"EmptyForIteratorPad\"/>-->\n        <module name=\"MethodParamPad\"/>\n        <module name=\"NoWhitespaceAfter\"/>\n        <module name=\"NoWhitespaceBefore\"/>\n        <module name=\"OperatorWrap\"/>\n        <module name=\"ParenPad\"/>\n        <module name=\"TypecastParenPad\"/>\n        <module name=\"WhitespaceAfter\"/>\n        <module name=\"WhitespaceAround\"/>\n\n\n        <!-- Modifier Checks                                    -->\n        <!-- See http://checkstyle.sf.net/config_modifiers.html -->\n        <module name=\"ModifierOrder\"/>\n        <module name=\"RedundantModifier\"/>\n\n\n        <!-- Checks for blocks. You know, those {}'s         -->\n        <!-- See http://checkstyle.sf.net/config_blocks.html -->\n        <module name=\"AvoidNestedBlocks\"/>\n        <module name=\"EmptyBlock\"/>\n        <module name=\"EmptyCatchBlock\">\n            <property name=\"exceptionVariableName\" value=\"expected|ignore\"/>\n        </module>\n        <module name=\"LeftCurly\">\n            <!-- No METHOD_DEF, because I think simple method can be one line -->\n            <property name=\"tokens\" value=\"INTERFACE_DEF, CLASS_DEF, ANNOTATION_DEF, ENUM_DEF,\n            CTOR_DEF, ENUM_CONSTANT_DEF, LITERAL_WHILE, LITERAL_TRY, LITERAL_CATCH,\n            LITERAL_FINALLY, LITERAL_SYNCHRONIZED, LITERAL_SWITCH, LITERAL_DO, LITERAL_IF,\n            LITERAL_ELSE, LITERAL_FOR, STATIC_INIT, OBJBLOCK, LAMBDA\"/>\n        </module>\n        <module name=\"NeedBraces\">\n            <property name=\"allowSingleLineStatement\" value=\"true\"/>\n        </module>\n        <module name=\"RightCurly\"/>\n\n\n        <!-- Checks for common coding problems               -->\n        <!-- See http://checkstyle.sf.net/config_coding.html -->\n        <!--module name=\"AvoidInlineConditionals\"/-->\n        <module name=\"CovariantEquals\"/>\n        <module name=\"EmptyStatement\"/>\n        <!--<module name=\"EqualsAvoidNull\"/>-->\n        <module name=\"EqualsHashCode\"/>\n        <!--module name=\"HiddenField\"/-->\n        <module name=\"IllegalInstantiation\"/>\n        <!--<module name=\"InnerAssignment\"/>-->\n        <!--module name=\"MagicNumber\"/-->\n        <module name=\"MissingSwitchDefault\"/>\n        <!--<module name=\"RedundantThrows\"/>-->\n        <module name=\"SimplifyBooleanExpression\"/>\n        <module name=\"SimplifyBooleanReturn\"/>\n\n        <!-- Checks for class design                         -->\n        <!-- See http://checkstyle.sf.net/config_design.html -->\n        <!--module name=\"DesignForExtension\"/-->\n        <!--module name=\"FinalClass\"/-->\n        <!--module name=\"HideUtilityClassConstructor\"/-->\n        <!--module name=\"InterfaceIsType\"/-->\n        <!--module name=\"VisibilityModifier\"/-->\n\n\n        <!-- Miscellaneous other checks.                   -->\n        <!-- See http://checkstyle.sf.net/config_misc.html -->\n        <!--module name=\"ArrayTypeStyle\"/-->\n        <!--module name=\"FinalParameters\"/-->\n        <!--module name=\"TodoComment\"/-->\n        <module name=\"UpperEll\"/>\n    </module>\n</module>"
  },
  {
    "path": "demo/.gitignore",
    "content": "/build\n"
  },
  {
    "path": "demo/build.gradle",
    "content": "apply plugin: 'com.android.application'\n\nandroid {\n    signingConfigs {\n        release {\n            keyAlias 'FileDownloaderDemoKey'\n            keyPassword 'liulishuo'\n            storeFile file('filedownloaderdemo.jks')\n            storePassword 'liulishuo'\n        }\n    }\n    compileSdkVersion COMPILE_SDK_VERSION as int\n    buildToolsVersion BUILD_TOOLS_VERSION as String\n\n    defaultConfig {\n        minSdkVersion 14\n        targetSdkVersion COMPILE_SDK_VERSION as int\n\n        File file = project.rootProject.file('local.properties');\n        def needLog = null\n        if (file.exists()) {\n            Properties p = new Properties()\n            p.load(file.newDataInputStream())\n            needLog = p.getProperty(\"needLog\")\n        }\n        buildConfigField \"boolean\", \"DOWNLOAD_NEED_LOG\", needLog == \"true\" ? \"true\" : \"false\"\n    }\n    buildTypes {\n        release {\n            minifyEnabled true\n            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'\n            signingConfig signingConfigs.release\n        }\n\n        debug {\n            minifyEnabled false\n        }\n    }\n    lintOptions {\n        // This seems to be firing due to okio referencing java.nio.File\n        // which is harmless for us.\n        warning 'InvalidPackage'\n    }\n}\n\ndependencies {\n    implementation 'com.android.support:appcompat-v7:28.0.0'\n    implementation 'com.android.support:recyclerview-v7:28.0.0'\n    implementation 'com.android.support:design:28.0.0'\n    debugImplementation 'cn.dreamtobe.threaddebugger:threaddebugger:1.3.3'\n    releaseImplementation 'cn.dreamtobe.threaddebugger:threaddebugger-no-op:1.3.3'\n    implementation project(':library')\n    // for testing\n    implementation 'com.squareup.okio:okio:1.14.0'\n}\n"
  },
  {
    "path": "demo/proguard-rules.pro",
    "content": "# filedownloader uses okhttp3-lib, so need add below proguard rules.\n-dontwarn okhttp3.*\n-dontwarn okio.**"
  },
  {
    "path": "demo/src/main/AndroidManifest.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<manifest xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    package=\"com.liulishuo.filedownloader.demo\">\n\n    <uses-permission android:name=\"android.permission.INTERNET\" />\n    <!-- When you invoke BaseDownloadTask#setWifiRequired(true), you need declare ACCESS_NETWORK_STATE permission -->\n    <uses-permission android:name=\"android.permission.ACCESS_NETWORK_STATE\" />\n    <uses-permission\n        android:name=\"android.permission.READ_EXTERNAL_STORAGE\"\n        android:maxSdkVersion=\"18\" />\n    <uses-permission\n        android:name=\"android.permission.WRITE_EXTERNAL_STORAGE\"\n        android:maxSdkVersion=\"18\" />\n\n    <application\n        android:name=\".DemoApplication\"\n        android:allowBackup=\"true\"\n        android:icon=\"@mipmap/ic_launcher\"\n        android:label=\"@string/app_name\"\n        android:networkSecurityConfig=\"@xml/network_security_config\"\n        android:supportsRtl=\"true\"\n        android:theme=\"@style/AppTheme\">\n        <activity android:name=\".MainActivity\">\n            <intent-filter>\n                <action android:name=\"android.intent.action.MAIN\" />\n\n                <category android:name=\"android.intent.category.LAUNCHER\" />\n            </intent-filter>\n        </activity>\n        <activity\n            android:name=\".HybridTestActivity\"\n            android:label=\"@string/hybrid_test_title\" />\n        <activity\n            android:name=\".SingleTaskTestActivity\"\n            android:label=\"@string/single_task_test_title\" />\n        <activity\n            android:name=\".MultitaskTestActivity\"\n            android:label=\"@string/multitask_test_title\" />\n        <activity\n            android:name=\".performance.PerformanceTestActivity\"\n            android:label=\"@string/performance_test_title\" />\n        <activity\n            android:name=\".TasksManagerDemoActivity\"\n            android:label=\"@string/tasks_manager_demo_title\" />\n        <activity\n            android:name=\".NotificationSampleActivity\"\n            android:label=\"@string/notification_sample_title\" />\n    </application>\n\n</manifest>"
  },
  {
    "path": "demo/src/main/assets/filedownloader.properties",
    "content": "# If you occur exception: 'can't know the size of the download file, and its Transfer-Encoding is not Chunked either',\n# but you want to ignore such exception, set true, will deal with it as the case of transfer encoding chunk. default false\n#\n# If true, will ignore HTTP response header does not has content-length either not chunk transfer\n# encoding.\n#\n# Default false.\nhttp.lenient=false\n\n# The FileDownloadService runs in the separate process ':filedownloader' as default, if you want to\n# run the FileDownloadService in the main process, just set true. default false.\nprocess.non-separate=false\n\n# The min buffered so far bytes.\n#\n# Used for adjudging whether is time to sync the downloaded so far bytes to database and make sure\n# sync the downloaded buffer to local file.\n#\n# More smaller more frequently, then download more slowly, but will more safer in scene of the\n# process is killed unexpectedly.\n#\n# Default 65536, which follow the value in com.android.providers.downloads.Constants.\ndownload.min-progress-step=65536\n\n# The min buffered millisecond.\n#\n# Used for adjudging whether is time to sync the downloaded so far bytes to database and make sure\n# sync the downloaded buffer to local file.\n#\n# More smaller more frequently, then download more slowly, but will more safer in scene of the\n# process is killed unexpectedly.\n#\n# Default 2000, which follow the value in com.android.providers.downloads.Constants.\ndownload.min-progress-time=2000\n\n# The maximum network thread count for downloading simultaneously.\n#\n# FileDownloader is designed to download 3 files simultaneously as maximum size as default, and the\n# rest of the task is in the FIFO(First In First Out) pending queue.\n#\n# Because the network resource is limited to one device, it means if FileDownloader start\n# downloading tasks unlimited simultaneously, it will be blocked by lack of the network resource,\n# and more useless CPU occupy.\n#\n# The relative efficiency of 3 is higher than others(As Fresco or Picasso do), But for case by case\n# FileDownloader is support to configure for this.\n#\n# Default 3.\n# max 12, min 1. If the value more than {@code max} will be replaced with {@code max}; If the value\n# less than {@code min} will be replaced with {@code min}.\ndownload.max-network-thread-count=3\n\n# Whether need to pre-allocates the 'content-length' space when start downloading.\n#\n# FileDownloader is designed to create the file and pre-allocates the 'content-length' space for it\n# when start downloading.\n#\n# Because FileDownloader want to prevent the space is not enough to store coming data in downloading\n# state as default.\n#\n# Default false.\n#\nfile.non-pre-allocation=false\n\n# Whether need to post an broadcast when downloading is completed.\n#\n# This option is very useful when you download something silent on the background on the filedownloader\n# process, and the main process is killed, but you want to do something on the main process when tasks\n# are completed downloading on the filedownloader process, so you can set this one to `true`, then\n# when a task is completed task, you will receive the broadcast, and the main process will be relaunched\n# to handle the broadcast.\n#\n# If you want to receive such broadcast, you also need to register receiver with\n# 'filedownloader.intent.action.completed' action name on 'AndroidManifest.xml'.\n#\n# You can use FileDownloadBroadcastHandler class to parse the received intent.\n#\n# Default false.\n#\nbroadcast.completed=false\n\n# Whether you want the first trial connection with HEAD method to request to backend or not.\n#\n# if this value is true, the first trial connection will with HEAD method instead of GET method and\n# then you will reduce 1 byte cost on the response body, but if the backend can't support HEAD\n# method you will receive 405 response code and failed to download.\n#\n# Default false.\n#\ndownload.trial-connection-head-method=false\n"
  },
  {
    "path": "demo/src/main/java/com/liulishuo/filedownloader/demo/Constant.java",
    "content": "package com.liulishuo.filedownloader.demo;\n\n/**\n * Created by Jacksgong on 12/19/15.\n */\npublic interface Constant {\n\n    String[] CHUNKED_TRANSFER_ENCODING_DATA_URLS = {\n            \"http://www.httpwatch.com/httpgallery/chunked/chunkedimage.aspx?0.04400023248109086\",\n    };\n\n    String LIULISHUO_APK_URL = \"http://cdn.llsapp.com/android/LLS-v4.0-595-20160908-143200.apk\";\n    String LIULISHUO_CONTENT_DISPOSITION_FILENAME = \"LLS-v4.0-595-20160908-143200.apk\";\n\n    String[] BIG_FILE_URLS = {\n            // 5m\n            \"http://mirror.internode.on.net/pub/test/5meg.test5\",\n            // 6m\n            \"http://download.chinaunix.net/down.php?id=10608&ResourceID=5267&site=1\",\n            // 8m\n            \"http://7xjww9.com1.z0.glb.clouddn.com/Hopetoun_falls.jpg\",\n            // 10m\n            \"http://dg.101.hk/1.rar\",\n            // 342m\n            \"http://180.153.105.144/dd.myapp.com/16891/E2F3DEBB12A049ED921C6257C5E9FB11.apk\",\n//            \"http://mirror.internode.on.net/pub/test/5meg.test4\",\n//            \"http://mirror.internode.on.net/pub/test/5meg.test3\",\n//            \"http://mirror.internode.on.net/pub/test/5meg.test2\",\n//            \"http://mirror.internode.on.net/pub/test/5meg.test1\",\n            // 6.8m\n//            \"http://dlsw.baidu.com/sw-search-sp/soft/7b/33461/freeime.1406862029.exe\",\n            // 10m\n//            \"http://mirror.internode.on.net/pub/test/10meg.test\",\n//            \"http://mirror.internode.on.net/pub/test/10meg.test1\",\n//            \"http://mirror.internode.on.net/pub/test/10meg.test2\",\n//            \"http://mirror.internode.on.net/pub/test/10meg.test3\",\n            // 10m\n            \"http://mirror.internode.on.net/pub/test/10meg.test4\",\n//            \"http://mirror.internode.on.net/pub/test/10meg.test5\",\n            // 20m\n            \"http://www.pc6.com/down.asp?id=72873\",\n            // 22m\n            \"http://113.207.16.84/dd.myapp.com/16891/2E53C25B6BC55D3330AB85A1B7B57485.apk?mkey=5630b43973f537cf&f=cf87&fsname=com.htshuo.htsg_3.0.1_49.apk&asr=02f1&p=.apk\",\n            // 206m\n            \"http://down.tech.sina.com.cn/download/d_load.php?d_id=49535&down_id=1&ip=42.81.45.159\"\n    };\n\n    String[] URLS = {\n            // 随机小资源一般不超过10\n            \"http://girlatlas.b0.upaiyun.com/35/20150106/19152b4c633b321f4479.jpg!mid\",\n            \"http://cdn-l.llsapp.com/connett/25183b40-22f2-0133-6e99-029df5130f9e\",\n            \"http://cdn-l.llsapp.com/connett/c3115411-3669-466d-8ef2-e6c42c690303\",\n            \"http://cdn-l.llsapp.com/connett/a55b4727-e228-410f-b44a-0385dbe9ab85\",\n            \"http://cdn-l.llsapp.com/connett/7b6b5485-0d19-476c-816c-ff6523fae539\",\n            \"http://cdn-l.llsapp.com/connett/33fa9155-c99a-407f-8d2c-82e9d17f4c32\",\n            \"http://cdn-l.llsapp.com/connett/fe50a391-d111-44a9-9c2f-33aaaeec9186\",\n            \"http://cdn.llsapp.com/crm_test_1449051526097.jpg\",\n            \"http://cdn.llsapp.com/crm_test_1449554617476.jpeg\",\n            \"http://cdn.llsapp.com/yy/image/3b0430db-5ff4-455c-9c8d-0213eea7b6c4.jpg\",\n            \"http://cdn.llsapp.com/forum/image/ba80be187e0947f2b60c763a04910948_1446722022222.jpg\",\n            \"http://cdn.llsapp.com/forum/image/NTNjMWQwMDAwMDAwMGQ0Zg==_1446122845.jpg\",\n            \"http://cdn.llsapp.com/user_images/FEFC55C5-1E8F-45C6-AA4E-79FC79F97B6F\",\n            \"http://cdn.llsapp.com/user_images/26ebf7deb8eb1f66056cbdac31aa18209d2f7daf_1436262740.jpg\",\n            \"http://cdn.llsapp.com/yy/image/a1de0e33-c3f3-4795-b2b9-4dafbcf06bee.jpg\",\n            \"http://cdn.llsapp.com/yy/image/cc4bc37d-ef77-4469-a8e9-2c70105a3f94.jpg\",\n            // 重复\n            \"http://cdn.llsapp.com/yy/image/cc4bc37d-ef77-4469-a8e9-2c70105a3f94.jpg\",\n            \"http://cdn.llsapp.com/yy/image/dd72c879-b1c4-4fb9-b871-d57dfa3aa709.jpg\",\n            \"http://cdn.llsapp.com/crm_test_1447220020113.jpg\",\n            \"http://cdn.llsapp.com/crm_test_1447220428493.jpg\",\n            // 重复\n            \"http://cdn.llsapp.com/yy/image/a1de0e33-c3f3-4795-b2b9-4dafbcf06bee.jpg\",\n            \"http://cdn.llsapp.com/forum/image/72e344b20d48432487389f8ad0dec163_1435047695818.png\",\n            \"http://cdn.llsapp.com/forum/image/36d3070792b14633ad1f596c38f892e2_1435047020634.jpg\",\n            \"http://cdn.llsapp.com/yy/image/5d8bfbd4-51b8-4fe6-ba01-4a5f37c478a6.jpg\",\n            \"http://cdn.llsapp.com/forum/image/M2YwMWQwMDAwMDAwMTBmYw==_1440748066.jpg\",\n            \"http://cdn.llsapp.com/forum/image/22f8389542734b05986c0b0dd8fd1735_1435230013392.jpg\",\n            \"http://cdn.llsapp.com/forum/image/2e6b8f9676aa47228aad74dd37709b0e_1446202991820.jpg\",\n            \"http://cdn.llsapp.com/forum/image/f82192fa9f764af396579e51afeb9aaf_1435049606128.jpg\",\n            \"http://cdn.llsapp.com/forum/image/f74026981afa42e0b73a6983450deca1_1441780286505.jpg\",\n            \"http://cdn.llsapp.com/357070051859561_1390016094611.jpg\",\n            // 重复\n            \"http://cdn-l.llsapp.com/connett/7b6b5485-0d19-476c-816c-ff6523fae539\",\n            \"http://cdn.llsapp.com/forum/image/6f7a673ea1224019bf73bb2301f61b26_1435211914955.jpg\",\n            \"http://cdn.llsapp.com/forum/image/a58b054f250e4237bd7d914c1feafc05_1435211918877.jpg\",\n            // 重复\n            \"http://cdn.llsapp.com/forum/image/f74026981afa42e0b73a6983450deca1_1441780286505.jpg\",\n            \"http://cdn.llsapp.com/forum/image/432f360f3a1b4436b569c1a58c0dffe4_1435917578613.jpg\",\n            \"http://cdn.llsapp.com/forum/image/a704f63a5b904961b71ea04b8a6aa36d_1397448248398.jpg\",\n            \"http://cdn.llsapp.com/yy/image/52f4abdb-5f7f-46c2-9095-cce5fc09b296.png\",\n            \"http://placekitten.com/580/320\",\n            \"http://cdn.llsapp.com/forum/image/MWIwMWQwMDAwMDAwMTA2Yw==_1436253885.jpg\",\n            \"http://cdn.llsapp.com/forum/image/2f003721ddb74ea1a84b2a6e603d6a44_1435046970863.jpg\",\n            \"http://cdn.llsapp.com/crm_test_1447219868528.jpg\",\n            \"http://cdn.llsapp.com/crm_test_1438658295447.jpg\",\n\n\n            // ---------------\n            \"http://imgsrc.baidu.com/forum/w%3D580/sign=2d51fcce8ad4b31cf03c94b3b7d7276f/48084b36acaf2eddc470ae648c1001e9380193bf.jpg\",\n            \"http://imgsrc.baidu.com/forum/w%3D580/sign=724ad1b57af40ad115e4c7eb672d1151/eb638535e5dde711e8dea57ba6efce1b9d166134.jpg\",\n            \"http://imgsrc.baidu.com/forum/w%3D580/sign=7155b164f3d3572c66e29cd4ba126352/d91090ef76c6a7efb59f4eabfcfaaf51f2de6687.jpg\",\n            \"http://imgsrc.baidu.com/forum/w%3D580/sign=4e454f3fd058ccbf1bbcb53229d9bcd4/c6188618367adab4b776fdce8ad4b31c8601e49f.jpg\",\n            \"http://imgsrc.baidu.com/forum/w%3D580/sign=e8431073c2fdfc03e578e3b0e43e87a9/af8ea0ec08fa513d06288a533c6d55fbb3fbd9ba.jpg\",\n            \"http://imgsrc.baidu.com/forum/w%3D580/sign=5a046dafc9fcc3ceb4c0c93ba244d6b7/5cd1f703918fa0ec15ee8580279759ee3d6ddb17.jpg\",\n            \"http://imgsrc.baidu.com/forum/w%3D580/sign=743d1d8071cf3bc7e800cde4e101babd/3c097bf40ad162d9e1e7839110dfa9ec8a13cd37.jpg\",\n            \"http://imgsrc.baidu.com/forum/w%3D580/sign=10ee22be728da9774e2f86238050f872/32d6912397dda144f74a8d3fb3b7d0a20df486f9.jpg\",\n            \"http://imgsrc.baidu.com/forum/w%3D580/sign=7a4eaf490bd162d985ee621421dea950/bb34e5dde71190ef0e807352cf1b9d16fcfa60c1.jpg\",\n            \"http://imgsrc.baidu.com/forum/w%3D580/sign=03f04c9f730e0cf3a0f74ef33a47f23d/e355564e9258d109207c483fd058ccbf6d814da5.jpg\",\n            \"http://imgsrc.baidu.com/forum/w%3D580/sign=10a0579bcefc1e17fdbf8c397a91f67c/c5f3b2119313b07e9989b0850dd7912397dd8c0c.jpg\",\n            \"http://imgsrc.baidu.com/forum/w%3D580/sign=eed1753686d6277fe9123230183a1f63/9dcd7cd98d1001e950d1f582b90e7bec55e7974b.jpg\",\n            \"http://imgsrc.baidu.com/forum/w%3D580/sign=4bd2cff1500fd9f9a0175561152cd42b/bc8aa61ea8d3fd1fe11e42b7314e251f95ca5f2d.jpg\",\n            // repeat case.\n            \"http://imgsrc.baidu.com/forum/w%3D580/sign=eed1753686d6277fe9123230183a1f63/9dcd7cd98d1001e950d1f582b90e7bec55e7974b.jpg\",\n            \"http://imgsrc.baidu.com/forum/w%3D580/sign=e7b4e3c7dbb44aed594ebeec831d876a/32f531adcbef76092a9a79122fdda3cc7dd99eaf.jpg\",\n            \"http://imgsrc.baidu.com/forum/w%3D580/sign=4d6e489206082838680ddc1c8898a964/3fe83901213fb80e11fd825a37d12f2eb9389414.jpg\",\n            \"http://imgsrc.baidu.com/forum/w%3D580/sign=299cf8ce8ad4b31cf03c94b3b7d7276f/48084b36acaf2eddc0bdaa648c1001e9380193f4.jpg\",\n            \"http://imgsrc.baidu.com/forum/w%3D580/sign=29d77fca3812b31bc76ccd21b6193674/a9dca144ad345982e8ed061f0df431adcaef84dd.jpg\",\n            // repeat case.\n            \"http://imgsrc.baidu.com/forum/w%3D580/sign=eed1753686d6277fe9123230183a1f63/9dcd7cd98d1001e950d1f582b90e7bec55e7974b.jpg\",\n            \"http://imgsrc.baidu.com/forum/w%3D580/sign=387f95ab6c224f4a5799731b39f69044/626134a85edf8db141979fe90823dd54574e7487.jpg\",\n            \"http://imgsrc.baidu.com/forum/w%3D580/sign=af2b5b0cca1349547e1ee86c664c92dd/b483b9014a90f60382797fca3812b31bb151ed70.jpg\",\n            \"http://imgsrc.baidu.com/forum/w%3D580/sign=291264639d82d158bb8259b9b00b19d5/8e50f8198618367a22af9d502f738bd4b31ce51f.jpg\",\n            \"http://imgsrc.baidu.com/forum/w%3D580/sign=c7d737439825bc312b5d01906ede8de7/d5c5b74543a9822625dec9aa8b82b9014a90eb26.jpg\",\n            \"http://imgsrc.baidu.com/forum/w%3D580/sign=77695d7638dbb6fd255be52e3925aba6/ae539822720e0cf3f179a7760b46f21fbf09aab6.jpg\",\n            \"http://imgsrc.baidu.com/forum/w%3D580/sign=614a17ae48540923aa696376a25ad1dc/87004a90f603738d458ce5afb21bb051f919ec7f.jpg\",\n            \"http://imgsrc.baidu.com/forum/w%3D580/sign=93d2e73ae850352ab16125006342fb1a/a13cf8dcd100baa16430af364610b912c8fc2e24.jpg\",\n            \"http://imgsrc.baidu.com/forum/w%3D580/sign=06184598bd315c6043956be7bdb3cbe6/894443a98226cffcf8f1563fb8014a90f703ea62.jpg\",\n            \"http://imgsrc.baidu.com/forum/w%3D580/sign=6db0c87337d3d539c13d0fcb0a86e927/413f6709c93d70cf9f9d4280f9dcd100bba12bdd.jpg\",\n            \"http://imgsrc.baidu.com/forum/w%3D580/sign=9560edf3241f95caa6f592bef9167fc5/2131e924b899a9014e62b3bb1c950a7b0308f5ed.jpg\",\n            \"http://imgsrc.baidu.com/forum/w%3D580/sign=a1cf8608c8ea15ce41eee00186023a25/9987c9177f3e67096bc9ad723ac79f3df9dc5577.jpg\",\n            \"http://imgsrc.baidu.com/forum/w%3D580/sign=eecf90f721a446237ecaa56aa8207246/60de8db1cb1349548ace02e9574e9258d0094a68.jpg\",\n            \"http://imgsrc.baidu.com/forum/w%3D580/sign=13560b451b4c510faec4e21250582528/0dfb828ba61ea8d3690da189960a304e251f5815.jpg\",\n            \"http://imgsrc.baidu.com/forum/w%3D580/sign=eecf90f721a446237ecaa56aa8207246/60de8db1cb1349548ace02e9574e9258d0094a68.jpg\",\n            // repeat case.\n            \"http://imgsrc.baidu.com/forum/w%3D580/sign=eecf90f721a446237ecaa56aa8207246/60de8db1cb1349548ace02e9574e9258d0094a68.jpg\",\n            \"http://imgsrc.baidu.com/forum/w%3D580/sign=eecf90f721a446237ecaa56aa8207246/60de8db1cb1349548ace02e9574e9258d0094a68.jpg\",\n            \"http://imgsrc.baidu.com/forum/w%3D580/sign=9bc331ae72f082022d9291377bfafb8a/1b2cd42a2834349b35b3bb08c8ea15ce37d3be83.jpg\",\n            \"http://imgsrc.baidu.com/forum/w%3D580/sign=862537777acb0a4685228b315b61f63e/ef08b3de9c82d15846698c3c810a19d8bd3e4251.jpg\",\n            \"http://imgsrc.baidu.com/forum/w%3D580/sign=9c1bda3ae850352ab16125006341fb1a/a13cf8dcd100baa16bf992364610b912c9fc2e63.jpg\",\n            \"http://imgsrc.baidu.com/forum/w%3D580/sign=420543d8d1160924dc25a213e405359b/32f2d7ca7bcb0a4606e7afb76a63f6246a60af7c.jpg\",\n            \"http://imgsrc.baidu.com/forum/w%3D580/sign=c544284d3b87e9504217f3642039531b/05c69f3df8dcd100c9e5dfaf738b4710b8122fc7.jpg\",\n            \"http://imgsrc.baidu.com/forum/w%3D580/sign=33776e678c1001e94e3c1407880f7b06/ee170924ab18972b44bc2744e7cd7b899f510abe.jpg\",\n            \"http://imgsrc.baidu.com/forum/w%3D580/sign=cea760b00df3d7ca0cf63f7ec21ebe3c/684f9258d109b3deca07c3e6cdbf6c81810a4c80.jpg\",\n            \"http://imgsrc.baidu.com/forum/w%3D580/sign=77aac436d53f8794d3ff4826e2190ead/189659ee3d6d55fb571451a86c224f4a21a4dd6b.jpg\",\n            \"http://imgsrc.baidu.com/forum/w%3D580/sign=ef6ca3c0cb8065387beaa41ba7dca115/fdcfc3fdfc039245aaf7c7818694a4c27c1e25fa.jpg\",\n            \"http://imgsrc.baidu.com/forum/w%3D580/sign=ef0286e6d009b3deebbfe460fcbe6cd3/0713b31bb051f8193f5422c4dbb44aed2f73e7c8.jpg\",\n            \"http://imgsrc.baidu.com/forum/w%3D580/sign=fec55e532f738bd4c421b239918a876c/f5ee76094b36acaf0aacb7727dd98d1000e99cf4.jpg\",\n            \"http://imgsrc.baidu.com/forum/w%3D580/sign=48422e39e850352ab16125006342fb1a/a13cf8dcd100baa1bfa066354610b912c9fc2eb4.jpg\",\n            \"http://imgsrc.baidu.com/forum/w%3D580/sign=2d1268678c1001e94e3c1407880c7b06/ee170924ab18972b5ad92144e7cd7b899f510a59.jpg\",\n            \"http://imgsrc.baidu.com/forum/w%3D580/sign=6f9ee8a14034970a47731027a5cbd1c0/a02e070828381f302e69ad27a8014c086f06f0c9.jpg\",\n            \"http://imgsrc.baidu.com/forum/w%3D580/sign=ad5032c19f2f07085f052a08d925b865/b31101e93901213f92886e5255e736d12e2e9581.jpg\",\n            \"http://imgsrc.baidu.com/forum/w%3D580/sign=72992644838ba61edfeec827713597cc/b900a18b87d6277f5d8312b629381f30e824fca8.jpg\",\n            \"http://imgsrc.baidu.com/forum/w%3D580/sign=433f1f6f63d9f2d3201124e799ed8a53/dbdce71190ef76c69f24dba59c16fdfaae51674e.jpg\",\n            \"http://imgsrc.baidu.com/forum/w%3D580/sign=7300c036d53f8794d3ff4826e21a0ead/189659ee3d6d55fb53be55a86c224f4a21a4ddc1.jpg\",\n            \"http://imgsrc.baidu.com/forum/w%3D580/sign=4c9c60b74a36acaf59e096f44cd88d03/6fdb81cb39dbb6fdd515c6a80824ab18962b37f6.jpg\",\n            \"http://imgsrc.baidu.com/forum/w%3D580/sign=95b291bfa08b87d65042ab1737092860/10dca3cc7cd98d1027472fbf203fb80e7aec90a9.jpg\",\n            \"http://imgsrc.baidu.com/forum/w%3D580/sign=ef679b0fca1349547e1ee86c664f92dd/b483b9014a90f603c235bfc93812b31bb151edbc.jpg\",\n            \"http://imgsrc.baidu.com/forum/w%3D580/sign=5ecdc676a686c91708035231f93c70c6/97004c086e061d95c17c15b67af40ad162d9ca03.jpg\",\n            \"http://imgsrc.baidu.com/forum/w%3D580/sign=b9ddaf869a504fc2a25fb00dd5dfe7f0/312542a7d933c89547b0bbf5d01373f083020076.jpg\",\n            \"http://imgsrc.baidu.com/forum/w%3D580/sign=266158f421a446237ecaa56aa8237246/60de8db1cb1349544260caea574e9258d0094ac6.jpg\",\n            \"http://imgsrc.baidu.com/forum/w%3D580/sign=50b490faeaf81a4c2632ecc1e72b6029/8f3433fa828ba61ef454eaa14034970a314e5982.jpg\",\n            \"http://imgsrc.baidu.com/forum/w%3D580/sign=31cb148d8644ebf86d716437e9f8d736/823fb13533fa828bfcb5b06dfc1f4134960a5aae.jpg\",\n            \"http://imgsrc.baidu.com/forum/w%3D580/sign=a8bbd18371cf3bc7e800cde4e101babd/3c097bf40ad162d93d614f9210dfa9ec8b13cdb6.jpg\",\n            \"http://imgsrc.baidu.com/forum/w%3D580/sign=4e26126f63d9f2d3201124e799ee8a53/dbdce71190ef76c6923dd6a59c16fdfaae516755.jpg\",\n            \"http://imgsrc.baidu.com/forum/w%3D580/sign=2630ab09d1a20cf44690fed746084b0c/ec1a0ef41bd5ad6ea276486480cb39dbb7fd3cb5.jpg\",\n            \"http://imgsrc.baidu.com/forum/w%3D580/sign=2ed231861e30e924cfa49c397c0a6e66/1f3eb80e7bec54e71f0b3690b8389b504ec26a5d.jpg\",\n            \"http://imgsrc.baidu.com/forum/w%3D580/sign=a4dd5daeb812c8fcb4f3f6c5cc0292b4/5d2662d0f703918f76ba1244503d269758eec4d2.jpg\",\n            \"http://imgsrc.baidu.com/forum/w%3D580/sign=458d67a0d31b0ef46ce89856edc551a1/8cfa43166d224f4ac1eb5c9d08f790529922d1cb.jpg\",\n            \"http://imgsrc.baidu.com/forum/w%3D580/sign=888b7ae7242dd42a5f0901a3333a5b2f/7f35970a304e251fca6bcb76a686c9177e3e53a4.jpg\",\n            \"http://imgsrc.baidu.com/forum/w%3D580/sign=5d2fcb76a686c91708035231f93f70c6/97004c086e061d95c29e18b67af40ad163d9ca61.jpg\",\n            \"http://imgsrc.baidu.com/forum/w%3D580/sign=f4524e5937d12f2ece05ae687fc3d5ff/45889e510fb30f24cd19c38dc995d143ac4b03b9.jpg\",\n            \"http://imgsrc.baidu.com/forum/w%3D580/sign=f291960fca1349547e1ee86c664f92dd/b483b9014a90f603dfc3b2c93812b31bb151edca.jpg\",\n            \"http://imgsrc.baidu.com/forum/w%3D580/sign=c526d13e64380cd7e61ea2e59145ad14/fdfcfc039245d688a1679c2aa5c27d1ed31b24d3.jpg\",\n            \"http://imgsrc.baidu.com/forum/w%3D580/sign=145760379f510fb37819779fe932c893/55610c338744ebf8e8d64ab1d8f9d72a6159a79e.jpg\",\n            \"http://imgsrc.baidu.com/forum/w%3D580/sign=7255fcaf91ef76c6d0d2fb23ad17fdf6/ef1273f082025aaf33875045faedab64024f1a83.jpg\",\n            \"http://imgsrc.baidu.com/forum/w%3D580/sign=9a90a5bf4b90f60304b09c4f0913b370/f48165380cd7912387cfbdfaac345982b2b78015.jpg\",\n            \"http://imgsrc.baidu.com/forum/w%3D580/sign=8b32fb409825bc312b5d01906ede8de7/d5c5b74543a98226693b05a98b82b9014b90eb43.jpg\",\n            \"http://imgsrc.baidu.com/forum/w%3D580/sign=42dbcb747acb0a4685228b315b62f63e/ef08b3de9c82d1588297703f810a19d8bc3e4223.jpg\",\n            \"http://imgsrc.baidu.com/forum/w%3D580/sign=08986a78a6efce1bea2bc8c29f50f3e8/bc035aafa40f4bfb639ab7da024f78f0f63618f2.jpg\",\n            \"http://imgsrc.baidu.com/forum/w%3D580/sign=13a61069902397ddd679980c6983b216/ac44d688d43f8794d25c61a0d31b0ef41ad53a99.jpg\",\n            \"http://imgsrc.baidu.com/forum/w%3D580/sign=6f03d5a97e3e6709be0045f70bc69fb8/50071d950a7b0208b371166f63d9f2d3562cc881.jpg\",\n            \"http://imgsrc.baidu.com/forum/w%3D580/sign=4a44166f63d9f2d3201124e799ed8a53/dbdce71190ef76c6965fd2a59c16fdfaae5167ab.jpg\",\n            \"http://imgsrc.baidu.com/forum/w%3D580/sign=26e0d6ad48540923aa696376a259d1dc/87004a90f603738d022624acb21bb051f919ecd5.jpg\",\n            \"http://imgsrc.baidu.com/forum/w%3D580/sign=d835b08a5882b2b7a79f39cc01accb0a/9ac37d1ed21b0ef462a4b0d0dcc451da80cb3ef4.jpg\",\n            \"http://imgsrc.baidu.com/forum/w%3D580/sign=de8abf31a1ec08fa260013af69ef3d4d/8a8e8c5494eef01f13a0034be1fe9925bd317d8c.jpg\",\n            \"http://imgsrc.baidu.com/forum/w%3D580/sign=d079799210dfa9ecfd2e561f52d1f754/48c7a7efce1b9d16df5081eff2deb48f8d5464ad.jpg\",\n            \"http://imgsrc.baidu.com/forum/w%3D580/sign=a97a4b860dd79123e0e0947c9d365917/c2029245d688d43fe46e8a7c7c1ed21b0ff43b7d.jpg\",\n            \"http://imgsrc.baidu.com/forum/w%3D580/sign=4c307cfdfaf2b211e42e8546fa816511/4c8a4710b912c8fc9fc6ec43fd039245d6882103.jpg\",\n            \"http://imgsrc.baidu.com/forum/w%3D580/sign=714fb895d50735fa91f04eb1ae500f9f/cc1ebe096b63f624b137238d8644ebf81b4ca3d3.jpg\",\n            \"http://imgsrc.baidu.com/forum/w%3D580/sign=9ce120865243fbf2c52ca62b807fca1e/f310728b4710b9129241f370c2fdfc03934522b8.jpg\",\n            \"http://imgsrc.baidu.com/forum/w%3D580/sign=a708cb53aa64034f0fcdc20e9fc17980/f7eb15ce36d3d5395af30a4d3b87e950342ab077.jpg\",\n            \"http://imgsrc.baidu.com/forum/w%3D580/sign=7a6533855ab5c9ea62f303ebe53bb622/efc9a786c9177f3e29f7f98371cf3bc79e3d5679.jpg\",\n            \"http://imgsrc.baidu.com/forum/w%3D580/sign=a3c9e88dc995d143da76e42b43f18296/6f0ed9f9d72a6059c443e5942934349b023bbaea.jpg\",\n            \"http://imgsrc.baidu.com/forum/w%3D580/sign=ee9dd7737a899e51788e3a1c72a6d990/c8256b600c338744309f2bf2500fd9f9d62aa0e3.jpg\",\n            \"http://imgsrc.baidu.com/forum/w%3D580/sign=3c24e0e6cdbf6c81f7372ce08c3fb1d7/b819367adab44aed8ed5ba6ab21c8701a08bfba2.jpg\",\n            \"http://imgsrc.baidu.com/forum/w%3D580/sign=37a42ca98b82b9013dadc33b438ca97e/ad12b07eca806538f48fa39d96dda144ac3482dc.jpg\",\n            \"http://imgsrc.baidu.com/forum/w%3D580/sign=18cbc2a13b292df597c3ac1d8c305ce2/47300a55b319ebc43b6071178326cffc1e171620.jpg\",\n            \"http://imgsrc.baidu.com/forum/w%3D580/sign=599e78acb21bb0518f24b3200678da77/9f45ad345982b2b7204b4d4a30adcbef77099b6d.jpg\",\n            \"http://imgsrc.baidu.com/forum/w%3D580/sign=70bdbebd9345d688a302b2ac94c37dab/36fb513d269759ee8e2d1745b3fb43166c22dfc4.jpg\",\n            \"http://imgsrc.baidu.com/forum/w%3D580/sign=8288e6b14afbfbeddc59367748f1f78e/3d3a5bb5c9ea15ceeef49787b7003af33a87b223.jpg\",\n            \"http://imgsrc.baidu.com/forum/w%3D580/sign=5bf14e8d8644ebf86d716437e9f8d736/823fb13533fa828b968fea6dfc1f4134960a5a94.jpg\",\n            \"http://imgsrc.baidu.com/forum/w%3D580/sign=0c3a8cad48540923aa696376a259d1dc/87004a90f603738d28fc7eacb21bb051f919ec8f.jpg\",\n            \"http://imgsrc.baidu.com/forum/w%3D580/sign=b9ca35b00df3d7ca0cf63f7ec21dbe3c/684f9258d109b3debd6a96e6cdbf6c81810a4c63.jpg\",\n            \"http://imgsrc.baidu.com/forum/w%3D580/sign=52196d93d52a283443a636036bb4c92e/a90b304e251f95cae388ef38c8177f3e6709523b.jpg\",\n            \"http://imgsrc.baidu.com/forum/w%3D580/sign=3ca90e9d08f79052ef1f47363cf2d738/f51249540923dd544a43dae6d009b3de9c824808.jpg\",\n            \"http://imgsrc.baidu.com/forum/w%3D580/sign=05fda1ee342ac65c6705667bcbf3b21d/c6ddd100baa1cd114df10faeb812c8fcc2ce2dfd.jpg\",\n            \"http://imgsrc.baidu.com/forum/w%3D580/sign=6b303b354610b912bfc1f6f6f3fcfcb5/b412632762d0f70337aee95209fa513d2697c525.jpg\",\n            \"http://imgsrc.baidu.com/forum/w%3D580/sign=2ec657a98b82b9013dadc33b438ca97e/ad12b07eca806538ededd89d96dda144ad34823e.jpg\",\n            \"http://imgsrc.baidu.com/forum/w%3D580/sign=bd1cdc74d0c8a786be2a4a065708c9c7/8698a9014c086e06859643c503087bf40ad1cb07.jpg\",\n            \"http://imgsrc.baidu.com/forum/w%3D580/sign=2c704569902397ddd679980c6983b216/ac44d688d43f8794ed8a34a0d31b0ef41ad53ac3.jpg\",\n            \"http://imgsrc.baidu.com/forum/w%3D580/sign=42e1458d8644ebf86d716437e9f8d736/823fb13533fa828b8f9fe16dfc1f4134960a5a84.jpg\",\n            \"http://imgsrc.baidu.com/forum/w%3D580/sign=fb5b3683279759ee4a5060c382fa434e/ce1e3a292df5e0fe6a84db8f5d6034a85fdf72a5.jpg\",\n            \"http://imgsrc.baidu.com/forum/w%3D580/sign=e86ba9a59c16fdfad86cc6e6848e8cea/9a0e4bfbfbedab644ccb1f4ef636afc378311e87.jpg\",\n            \"http://imgsrc.baidu.com/forum/w%3D580/sign=9e9ffaa8fcfaaf5184e381b7bc5594ed/75fafbedab64034f28749088aec379310b551d87.jpg\",\n            \"http://imgsrc.baidu.com/forum/w%3D580/sign=ef3cd96f2e2eb938ec6d7afae56085fe/a0500fb30f2442a762e8272bd043ad4bd013025f.jpg\",\n            \"http://imgsrc.baidu.com/forum/w%3D580/sign=f612834e83025aafd3327ec3cbecab8d/ea2b2834349b033b7cb4395414ce36d3d539bd05.jpg\",\n            \"http://imgsrc.baidu.com/forum/w%3D580/sign=1258b9ea0823dd542173a760e108b3df/7491f603738da977e05943a5b151f8198718e3c8.jpg\",\n            \"http://imgsrc.baidu.com/forum/w%3D580/sign=b2bf6795d50735fa91f04eb1ae500f9f/cc1ebe096b63f62472c7fc8d8644ebf81b4ca3a3.jpg\",\n            \"http://imgsrc.baidu.com/forum/w%3D580/sign=33e53c016d81800a6ee58906813433d6/087bdab44aed2e73696943a28601a18b86d6faba.jpg\",\n            \"http://imgsrc.baidu.com/forum/w%3D580/sign=c00943a28601a18bf0eb1247ae2d0761/92ae2edda3cc7cd9c6cdf1573801213fb90e9159.jpg\",\n            \"http://imgsrc.baidu.com/forum/w%3D580/sign=927fba532f738bd4c421b2399189876c/f5ee76094b36acaf661653727dd98d1000e99c4f.jpg\",\n            \"http://imgsrc.baidu.com/forum/w%3D580/sign=66690260b58f8c54e3d3c5270a282dee/3d4e78f0f736afc3b009fbebb219ebc4b745123c.jpg\",\n            \"http://imgsrc.baidu.com/forum/w%3D580/sign=e90ec8acb21bb0518f24b320067bda77/9f45ad345982b2b790dbfd4a30adcbef77099bfd.jpg\",\n            \"http://imgsrc.baidu.com/forum/w%3D580/sign=331550faac345982c58ae59a3cf5310b/b995a4c27d1ed21baa3cea6bac6eddc450da3f4c.jpg\",\n            \"http://imgsrc.baidu.com/forum/w%3D580/sign=56e2d9861e30e924cfa49c397c0a6e66/1f3eb80e7bec54e7673bde90b8389b504ec26a6e.jpg\",\n            \"http://imgsrc.baidu.com/forum/w%3D580/sign=cc0a533586d6277fe912323018391f63/9dcd7cd98d1001e9720ad381b90e7bec54e7970f.jpg\",\n            \"http://imgsrc.baidu.com/forum/w%3D580/sign=2e0417737a899e51788e3a1c72a5d990/c8256b600c338744f006ebf2500fd9f9d62aa07a.jpg\",\n            \"http://imgsrc.baidu.com/forum/w%3D580/sign=504292e7242dd42a5f0901a3333a5b2f/7f35970a304e251f12a22376a686c9177e3e53ec.jpg\",\n            \"http://imgsrc.baidu.com/forum/w%3D580/sign=a42ca911a50f4bfb8cd09e5c334e788f/0a9a033b5bb5c9ea33e0c56dd439b6003af3b32a.jpg\",\n            \"http://imgsrc.baidu.com/forum/w%3D580/sign=39fe414542166d223877159c76220945/82305c6034a85edfe1b438ad48540923dd547501.jpg\",\n            \"http://imgsrc.baidu.com/forum/w%3D580/sign=807d27b934fae6cd0cb4ab693fb20f9e/80086b63f6246b601b6574faeaf81a4c500fa2d2.jpg\",\n            \"http://imgsrc.baidu.com/forum/w%3D580/sign=bfb3f0855ab5c9ea62f303ebe538b622/efc9a786c9177f3eec213a8371cf3bc79f3d562c.jpg\",\n            \"http://imgsrc.baidu.com/forum/w%3D580/sign=9835385177094b36db921be593cd7c00/e3c551da81cb39db1f65a1d8d1160924aa18309c.jpg\",\n            \"http://imgsrc.baidu.com/forum/w%3D580/sign=07e91f461b4c510faec4e21250582528/0dfb828ba61ea8d37db2b58a960a304e241f58a9.jpg\",\n            \"http://imgsrc.baidu.com/forum/w%3D580/sign=358f41318d5494ee87220f111df4e0e1/46f1f736afc37931cc0446a7eac4b74542a911d5.jpg\",\n            \"http://imgsrc.baidu.com/forum/w%3D580/sign=7f2e51b14afbfbeddc59367748f1f78e/3d3a5bb5c9ea15ce13522087b7003af33b87b285.jpg\",\n            \"http://imgsrc.baidu.com/forum/w%3D580/sign=f87b4980b03533faf5b6932698d2fdca/b5d5b31c8701a18b6675d2c19f2f07082938fea0.jpg\",\n            \"http://imgsrc.baidu.com/forum/w%3D580/sign=95c5ccacb21bb0518f24b320067bda77/9f45ad345982b2b7ec10f94a30adcbef77099bb6.jpg\",\n            \"http://imgsrc.baidu.com/forum/w%3D580/sign=c103ff9aaa18972ba33a00c2d6cc7b9d/45ca0a46f21fbe097a76009a6a600c338744ad11.jpg\",\n            \"http://imgsrc.baidu.com/forum/w%3D580/sign=293cff4991529822053339cbe7cb7b3b/77550923dd54564efd4727b7b2de9c82d1584f1b.jpg\",\n            \"http://imgsrc.baidu.com/forum/w%3D580/sign=8a8c48b76a63f6241c5d390bb745eb32/f2be6c81800a19d8c4ad478b32fa828ba71e4697.jpg\",\n            \"http://imgsrc.baidu.com/forum/w%3D580/sign=c5790d4cb3119313c743ffb855390c10/7911b912c8fcc3ce55c70abd9345d688d43f203e.jpg\",\n            \"http://imgsrc.baidu.com/forum/w%3D580/sign=9ca91d7ab64543a9f51bfac42e168a7b/f85d10385343fbf29da165adb17eca8064388fb4.jpg\",\n            \"http://imgsrc.baidu.com/forum/w%3D580/sign=af58c225023b5bb5bed720f606d2d523/abcbd1c8a786c917f85291b7c83d70cf3ac757e8.jpg\",\n            \"http://imgsrc.baidu.com/forum/w%3D580/sign=43ffae503c6d55fbc5c6762e5d234f40/13f4e0fe9925bc313908c3165fdf8db1ca1370ec.jpg\",\n            \"http://imgsrc.baidu.com/forum/w%3D580/sign=55a84b8f5d6034a829e2b889fb1249d9/7da88226cffc1e17460f4ebf4b90f603728de98a.jpg\",\n            \"http://imgsrc.baidu.com/forum/w%3D580/sign=15d4b519d833c895a67e9873e1117397/244d510fd9f9d72a7aa9d293d52a2834359bbb74.jpg\",\n            \"http://imgsrc.baidu.com/forum/w%3D580/sign=464226e6cdbf6c81f7372ce08c3fb1d7/b819367adab44aedf4b37c6ab21c8701a08bfb45.jpg\",\n            \"http://imgsrc.baidu.com/forum/w%3D580/sign=6da166ef0eb30f24359aec0bf894d192/32328744ebf81a4c47272147d62a6059252da62c.jpg\",\n            \"http://imgsrc.baidu.com/forum/w%3D580/sign=4fa1f2c503087bf47dec57e1c2d2575e/71c3d5628535e5ddb525685177c6a7efce1b6230.jpg\",\n            \"http://imgsrc.baidu.com/forum/w%3D580/sign=395088713ac79f3d8fe1e4388aa0cdbc/45f50ad162d9f2d3a741e961a8ec8a136227ccea.jpg\",\n            \"http://imgsrc.baidu.com/forum/w%3D580/sign=131e281c0df431adbcd243317b37ac0f/30f51bd5ad6eddc4f073797538dbb6fd536633ad.jpg\",\n            \"http://imgsrc.baidu.com/forum/w%3D580/sign=0793b7f421a446237ecaa56aa8237246/60de8db1cb134954639225ea574e9258d0094ab5.jpg\",\n            \"http://imgsrc.baidu.com/forum/w%3D580/sign=079bb7f421a446237ecaa56aa8237246/60de8db1cb134954639a25ea574e9258d0094abd.jpg\",\n            \"http://imgsrc.baidu.com/forum/w%3D580/sign=06d44b609d82d158bb8259b9b00819d5/8e50f8198618367a0d69b2532f738bd4b21ce55a.jpg\",\n            \"http://imgsrc.baidu.com/forum/w%3D580/sign=449150c93812b31bc76ccd21b6193674/a9dca144ad34598285ab291c0df431adcbef8418.jpg\",\n            \"http://imgsrc.baidu.com/forum/w%3D580/sign=3d611c63e61190ef01fb92d7fe1a9df7/934ad11373f08202e2fb5db14afbfbedaa641bd0.jpg\",\n            \"http://imgsrc.baidu.com/forum/w%3D580/sign=e51c7d68267f9e2f70351d002f31e962/42d88d1001e9390165a842b07aec54e737d19693.jpg\",\n            \"http://imgsrc.baidu.com/forum/w%3D580/sign=1b7c903f810a19d8cb03840d03fb82c9/e4b54aed2e738bd464df7bbfa08b87d6267ff940.jpg\",\n            \"http://imgsrc.baidu.com/forum/w%3D580/sign=80c4a70bc8ea15ce41eee00186023a25/9987c9177f3e67094ac28c713ac79f3df9dc557b.jpg\",\n            \"http://imgsrc.baidu.com/forum/w%3D580/sign=3ee83e70c2fdfc03e578e3b0e43e87a9/af8ea0ec08fa513dd083a4503c6d55fbb2fbd911.jpg\",\n            \"http://imgsrc.baidu.com/forum/w%3D580/sign=79dd546dfc1f4134e0370576151e95c1/197e9e2f07082838c0f3159ab999a9014d08f140.jpg\",\n            \"http://imgsrc.baidu.com/forum/w%3D580/sign=8668bbaeb812c8fcb4f3f6c5cc0192b4/5d2662d0f703918f540ff444503d269758eec460.jpg\",\n            \"http://imgsrc.baidu.com/forum/w%3D580/sign=a1406895d50735fa91f04eb1ae500f9f/cc1ebe096b63f6246138f38d8644ebf81b4ca3dc.jpg\",\n            \"http://imgsrc.baidu.com/forum/w%3D580/sign=65ea09bd728da9774e2f86238050f872/32d6912397dda144824ea63cb3b7d0a20df486fe.jpg\",\n            \"http://imgsrc.baidu.com/forum/w%3D580/sign=a1e8d7956159252da3171d0c049a032c/c31e4134970a304e5d0e9575d0c8a786c9175c15.jpg\",\n            \"http://imgsrc.baidu.com/forum/w%3D580/sign=022c49eb0823dd542173a760e108b3df/7491f603738da977f02db3a4b151f8198718e3c4.jpg\",\n            \"http://imgsrc.baidu.com/forum/w%3D580/sign=4e717b4b0bd162d985ee621421dea950/bb34e5dde71190ef3abfa750cf1b9d16fcfa60fd.jpg\",\n            \"http://imgsrc.baidu.com/forum/w%3D580/sign=296f01563801213fcf334ed464e636f8/9519972bd40735fa42b27b369f510fb30e2408fb.jpg\",\n            \"http://imgsrc.baidu.com/forum/w%3D580/sign=208cca3f64380cd7e61ea2e59146ad14/fdfcfc039245d68844cd872ba5c27d1ed31b2476.jpg\",\n            \"http://imgsrc.baidu.com/forum/w%3D580/sign=ac81b826a8014c08193b28ad3a7a025b/6ae636d12f2eb938def54f7dd4628535e4dd6fa1.jpg\",\n            \"http://imgsrc.baidu.com/forum/w%3D580/sign=4371a9538b13632715edc23ba18ea056/f01a9d16fdfaaf51a170b4308d5494eef11f7aaa.jpg\",\n            \"http://imgsrc.baidu.com/forum/w%3D580/sign=fbb28bac622762d0803ea4b790ed0849/a317fdfaaf51f3dee6d18deb95eef01f3b2979da.jpg\",\n            \"http://imgsrc.baidu.com/forum/w%3D580/sign=29a9f1a03b292df597c3ac1d8c305ce2/47300a55b319ebc40a0242168326cffc1f1716c3.jpg\",\n            \"http://imgsrc.baidu.com/forum/w%3D580/sign=ef1e24a094cad1c8d0bbfc2f4f3f67c4/d725b899a9014c08b1561c2a0b7b02087af4f4d5.jpg\",\n            \"http://imgsrc.baidu.com/forum/w%3D580/sign=64a783bd5bafa40f3cc6ced59b65038c/1635349b033b5bb5debd147137d3d539b700bcd3.jpg\",\n            \"http://imgsrc.baidu.com/forum/w%3D580/sign=840cbab17aec54e741ec1a1689399bfd/0bfbe6cd7b899e51aa800d9b43a7d933c8950d37.jpg\",\n            \"http://imgsrc.baidu.com/forum/w%3D580/sign=c7b6a25309fa513d51aa6cd60d6c554c/b25594eef01f3a297bcce2419825bc315c607c3d.jpg\",\n            \"http://imgsrc.baidu.com/forum/w%3D580/sign=b7b2d786b7003af34dbadc68052bc619/f73c70cf3bc79f3d79bdd3bfbba1cd11738b29e5.jpg\",\n            \"http://imgsrc.baidu.com/forum/w%3D580/sign=c52aa550cf1b9d168ac79a69c3dcb4eb/64aea40f4bfbfbed188801f079f0f736aec31f68.jpg\",\n            \"http://imgsrc.baidu.com/forum/w%3D580/sign=95073312caef76093c0b99971edfa301/936fddc451da81cba028b4425366d01608243177.jpg\",\n            \"http://imgsrc.baidu.com/forum/w%3D580/sign=f5ef186aac6eddc426e7b4f309dab6a2/714b20a4462309f76b499b9d730e0cf3d7cad618.jpg\",\n            \"http://imgsrc.baidu.com/forum/w%3D580/sign=aefb827438dbb6fd255be52e3925aba6/ae539822720e0cf328eb78740b46f21fbe09aa26.jpg\",\n            \"http://imgsrc.baidu.com/forum/w%3D580/sign=c378aef4d01373f0f53f6f97940e4b8b/5e58252dd42a283426a000845ab5c9ea15cebf3f.jpg\",\n            \"http://imgsrc.baidu.com/forum/w%3D580/sign=7a589ea9fcfaaf5184e381b7bc5594ed/75fafbedab64034fccb3f489aec379310b551dc7.jpg\",\n            \"http://imgsrc.baidu.com/forum/w%3D580/sign=1142d2bfbba1cd1105b672288913c8b0/692d11dfa9ec8a138ab9616ff603918fa1ecc09b.jpg\",\n            \"http://imgsrc.baidu.com/forum/w%3D580/sign=7d615fdf35a85edffa8cfe2b795509d8/bc27cffc1e178a827851492ff703738da877e8d5.jpg\",\n            \"http://imgsrc.baidu.com/forum/w%3D580/sign=2583da46d62a60595210e1121835342d/96d2fd1f4134970a44c226a094cad1c8a6865d88.jpg\",\n            \"http://imgsrc.baidu.com/forum/w%3D580/sign=be5aeaef342ac65c6705667bcbf0b21d/c6ddd100baa1cd11f65644afb812c8fcc2ce2d59.jpg\",\n            \"http://imgsrc.baidu.com/forum/w%3D580/sign=9bc33512caef76093c0b99971edca301/936fddc451da81cbaeecb2425366d01609243133.jpg\",\n            \"http://imgsrc.baidu.com/forum/w%3D580/sign=222ed0808694a4c20a23e7233ef51bac/67ef3d6d55fbb2fbb7b0699d4e4a20a44723dca3.jpg\",\n            \"http://imgsrc.baidu.com/forum/w%3D580/sign=e31989af00e9390156028d364bed54f9/3725ab18972bd407aa3ae2727a899e510eb30944.jpg\",\n            \"http://imgsrc.baidu.com/forum/w%3D580/sign=d0d4a8102fdda3cc0be4b82831eb3905/07dab6fd5266d01692c6afa7962bd40734fa3566.jpg\",\n            \"http://imgsrc.baidu.com/forum/w%3D580/sign=79dc818ae4dde711e7d243fe97eecef4/ef42ad4bd11373f02ebc5e10a50f4bfbfaed04ba.jpg\",\n            \"http://imgsrc.baidu.com/forum/w%3D580/sign=45257e4b0bd162d985ee621421dea950/bb34e5dde71190ef31eba250cf1b9d16fdfa6029.jpg\",\n            \"http://imgsrc.baidu.com/forum/w%3D580/sign=431115875243fbf2c52ca62b807fca1e/f310728b4710b9124db1c671c2fdfc03934522c9.jpg\",\n            \"http://imgsrc.baidu.com/forum/w%3D580/sign=9e4d62b6c83d70cf4cfaaa05c8ddd1ba/347a02087bf40ad183aaf76c562c11dfa8eccef0.jpg\",\n            \"http://imgsrc.baidu.com/forum/w%3D580/sign=4d261167bd3eb13544c7b7b3961fa8cb/10728bd4b31c87016ca78f69267f9e2f0708ff29.jpg\",\n            \"http://imgsrc.baidu.com/forum/w%3D580/sign=0a2041eb0823dd542173a760e108b3df/7491f603738da977f821bba4b151f8198618e330.jpg\",\n            \"http://imgsrc.baidu.com/forum/w%3D580/sign=cdc3469310dfa9ecfd2e561f52d1f754/48c7a7efce1b9d16c2eabeeef2deb48f8c546414.jpg\",\n            \"http://imgsrc.baidu.com/forum/w%3D580/sign=8c9786b5314e251fe2f7e4f09787c9c2/16391f30e924b89964a25db76f061d950b7bf6a0.jpg\",\n            \"http://imgsrc.baidu.com/forum/w%3D580/sign=194483fad788d43ff0a991fa4d1fd2aa/6f3c269759ee3d6db0bca34442166d224e4adecc.jpg\",\n            \"http://imgsrc.baidu.com/forum/w%3D580/sign=a77bfa7bb64543a9f51bfac42e168a7b/f85d10385343fbf2a67382acb17eca8064388fe6.jpg\",\n            \"http://imgsrc.baidu.com/forum/w%3D580/sign=f0f4189cdc54564ee565e43183df9cde/c802738da97739120abba1eef9198618377ae2a5.jpg\",\n            \"http://imgsrc.baidu.com/forum/w%3D580/sign=fd1452a96c224f4a5799731b39f59044/626134a85edf8db184fc58eb0823dd54574e746b.jpg\",\n            \"http://imgsrc.baidu.com/forum/w%3D580/sign=85354810a50f4bfb8cd09e5c334e788f/0a9a033b5bb5c9ea12f9246cd439b6003af3b333.jpg\",\n            \"http://imgsrc.baidu.com/forum/w%3D580/sign=71e47c10a50f4bfb8cd09e5c334d788f/0a9a033b5bb5c9eae628106cd439b6003bf3b363.jpg\",\n            \"http://imgsrc.baidu.com/forum/w%3D580/sign=84ab2ceab219ebc4c0787691b227cf79/d751352ac65c1038aed9dd4db3119313b17e899f.jpg\",\n            \"http://imgsrc.baidu.com/forum/w%3D580/sign=75d075fcfaf2b211e42e8546fa826511/4c8a4710b912c8fca626e542fd039245d788216c.jpg\",\n            \"http://imgsrc.baidu.com/forum/w%3D580/sign=2f963f67bd3eb13544c7b7b3961fa8cb/10728bd4b31c87010e17a169267f9e2f0608ff99.jpg\",\n            \"http://imgsrc.baidu.com/forum/w%3D580/sign=0da3978a32fa828bd1239debcd1e41cd/8d1d8701a18b87d696e2b890060828381e30fd9a.jpg\",\n            \"http://imgsrc.baidu.com/forum/w%3D580/sign=6c568139c8177f3e1034fc0540ce3bb9/72096e061d950a7bbf965d4b0bd162d9f3d3c99b.jpg\",\n            \"http://imgsrc.baidu.com/forum/w%3D580/sign=1e295079a6efce1bea2bc8c29f50f3e8/bc035aafa40f4bfb752b8ddb024f78f0f6361842.jpg\",\n            \"http://imgsrc.baidu.com/forum/w%3D580/sign=1932ba3dd058ccbf1bbcb53229dabcd4/c6188618367adab4e00108cc8ad4b31c8601e469.jpg\",\n            \"http://imgsrc.baidu.com/forum/w%3D580/sign=194aefa87e3e6709be0045f70bc59fb8/50071d950a7b0208c5382c6e63d9f2d3562cc849.jpg\",\n            \"http://imgsrc.baidu.com/forum/w%3D580/sign=4cdad5a0bf096b6381195e583c328733/ef59ccbf6c81800a5f449b81b03533fa838b4798.jpg\",\n            \"http://imgsrc.baidu.com/forum/w%3D580/sign=d0b7bf9d730e0cf3a0f74ef33a44f23d/e355564e9258d109f33bbb3dd058ccbf6d814d61.jpg\",\n            \"http://imgsrc.baidu.com/forum/w%3D580/sign=1d63677dd462853592e0d229a0ee76f2/e732c895d143ad4b57205b4f83025aafa40f0637.jpg\",\n            \"http://imgsrc.baidu.com/forum/w%3D580/sign=67e1443e810a19d8cb03840d03fb82c9/e4b54aed2e738bd41842afbea08b87d6267ff9db.jpg\",\n            \"http://imgsrc.baidu.com/forum/w%3D580/sign=204218f1241f95caa6f592bef9167fc5/2131e924b899a901fb4046b91c950a7b0308f5cd.jpg\",\n            \"http://imgsrc.baidu.com/forum/w%3D580/sign=0f01eb42fd039245a1b5e107b796a4a8/9eed08fa513d2697952115d254fbb2fb4216d854.jpg\",\n            \"http://imgsrc.baidu.com/forum/w%3D580/sign=21da248c8644ebf86d716437e9f8d736/823fb13533fa828beca4806cfc1f4134960a5abe.jpg\",\n            \"http://imgsrc.baidu.com/forum/w%3D580/sign=cefc37a88b82b9013dadc33b438ca97e/ad12b07eca8065380dd7b89c96dda144ad348204.jpg\",\n            \"http://imgsrc.baidu.com/forum/w%3D580/sign=316e0c92d52a283443a636036bb4c92e/a90b304e251f95ca80ff8e39c8177f3e67095233.jpg\",\n            \"http://imgsrc.baidu.com/forum/w%3D580/sign=835423eab219ebc4c0787691b227cf79/d751352ac65c1038a926d24db3119313b17e89e2.jpg\",\n            \"http://imgsrc.baidu.com/forum/w%3D580/sign=d5ac1f6e2e2eb938ec6d7afae56385fe/a0500fb30f2442a75878e12ad043ad4bd01302cf.jpg\",\n            \"http://imgsrc.baidu.com/forum/w%3D580/sign=273ea5eb95eef01f4d1418cdd0ff99e0/c937afc379310a5520a8c27bb64543a9832610b5.jpg\",\n            \"http://imgsrc.baidu.com/forum/w%3D580/sign=1cfea33db8014a90813e46b599753971/8e7fca8065380cd793cabe62a044ad345882816d.jpg\",\n            \"http://imgsrc.baidu.com/forum/w%3D580/sign=a4bc83be4d086e066aa83f4332097b5a/08d02f2eb9389b5053e7ffdd8435e5dde7116e21.jpg\",\n            \"http://imgsrc.baidu.com/forum/w%3D580/sign=b00b9181b03533faf5b6932698d1fdca/b5d5b31c8701a18b2e050ac09f2f07082938fe50.jpg\",\n            \"http://imgsrc.baidu.com/forum/w%3D580/sign=61c8bd5077c6a7efb926a82ecdf8afe9/4df182025aafa40fcd22d652aa64034f79f0195d.jpg\",\n            \"http://imgsrc.baidu.com/forum/w%3D580/sign=d252e43f64380cd7e61ea2e59145ad14/fdfcfc039245d688b613a92ba5c27d1ed21b2428.jpg\",\n            \"http://imgsrc.baidu.com/forum/w%3D580/sign=696f7410a50f4bfb8cd09e5c334e788f/0a9a033b5bb5c9eafea3186cd439b6003bf3b3ea.jpg\",\n            \"http://imgsrc.baidu.com/forum/w%3D580/sign=5d9e8d737dd98d1076d40c39113eb807/6c67d0160924ab18e468fab834fae6cd7a890bc7.jpg\",\n            \"http://imgsrc.baidu.com/forum/w%3D580/sign=7e7d9a308d5494ee87220f111df4e0e1/46f1f736afc3793187f69da6eac4b74542a911a7.jpg\",\n            \"http://imgsrc.baidu.com/forum/w%3D580/sign=541e1764b7fd5266a72b3c1c9b1a9799/a623720e0cf3d7caae1e24f9f31fbe096a63a952.jpg\",\n            \"http://imgsrc.baidu.com/forum/w%3D580/sign=0cdeec71c2fdfc03e578e3b0e43e87a9/af8ea0ec08fa513de2b576513c6d55fbb2fbd927.jpg\",\n            \"http://imgsrc.baidu.com/forum/w%3D580/sign=6903fb952934349b74066e8df9eb1521/0e4f251f95cad1c8eba8e6a87e3e6709c93d512a.jpg\",\n            \"http://imgsrc.baidu.com/forum/w%3D580/sign=29f189a1a9d3fd1f3609a232004f25ce/b9d7277f9e2f07088342308fe824b899a801f2ff.jpg\",\n            \"http://imgsrc.baidu.com/forum/w%3D580/sign=4582d8bc908fa0ec7fc764051696594a/cddfb48f8c5494eed74d15962cf5e0fe98257ed6.jpg\",\n            \"http://imgsrc.baidu.com/forum/w%3D580/sign=6843433e810a19d8cb03840d03f882c9/e4b54aed2e738bd417e0a8bea08b87d6267ff979.jpg\",\n            \"http://imgsrc.baidu.com/forum/w%3D580/sign=29db73df35a85edffa8cfe2b795609d8/bc27cffc1e178a822ceb652ff703738da877e86f.jpg\",\n            \"http://imgsrc.baidu.com/forum/w%3D580/sign=baae3c4b30adcbef01347e0e9cae2e0e/25d4ad6eddc451daebc70964b7fd5266d0163208.jpg\",\n            \"http://imgsrc.baidu.com/forum/w%3D580/sign=724142344610b912bfc1f6f6f3fcfcb5/b412632762d0f7032edf905309fa513d2797c5d5.jpg\",\n            \"http://imgsrc.baidu.com/forum/w%3D580/sign=470c066cd439b6004dce0fbfd9513526/5908c93d70cf3bc7cdffc863d000baa1cc112a46.jpg\",\n            \"http://imgsrc.baidu.com/forum/w%3D580/sign=fcc28826a8014c08193b28ad3a79025b/6ae636d12f2eb9388eb67f7dd4628535e4dd6f62.jpg\",\n            \"http://imgsrc.baidu.com/forum/w%3D580/sign=0ad08c8e5d6034a829e2b889fb1149d9/7da88226cffc1e17197789be4b90f603728de972.jpg\",\n            \"http://imgsrc.baidu.com/forum/w%3D580/sign=2a743d8c8644ebf86d716437e9f8d736/823fb13533fa828be70a996cfc1f4134970a5a10.jpg\",\n            \"http://imgsrc.baidu.com/forum/w%3D580/sign=80a8b07438dbb6fd255be52e3925aba6/ae539822720e0cf306b84a740b46f21fbf09aaf7.jpg\",\n            \"http://imgsrc.baidu.com/forum/w%3D580/sign=cae0a3e7d009b3deebbfe460fcbe6cd3/0713b31bb051f8191ab607c5dbb44aed2f73e7ab.jpg\",\n            \"http://imgsrc.baidu.com/forum/w%3D580/sign=28e2af9006082838680ddc1c8898a964/3fe83901213fb80e7471655837d12f2eb8389499.jpg\",\n            \"http://imgsrc.baidu.com/forum/w%3D580/sign=193e3c4b1ad5ad6eaaf964e2b1ca39a3/53234f4a20a44623c2d2a2ed9922720e0cf3d722.jpg\",\n            \"http://imgsrc.baidu.com/forum/w%3D580/sign=8ca6933486d6277fe912323018391f63/9dcd7cd98d1001e932a61380b90e7bec55e797a3.jpg\",\n            \"http://imgsrc.baidu.com/forum/w%3D580/sign=2d0d6fdf35a85edffa8cfe2b795509d8/bc27cffc1e178a82283d792ff703738da977e839.jpg\",\n            \"http://imgsrc.baidu.com/forum/w%3D580/sign=1b3c484b0bd162d985ee621421dea950/bb34e5dde71190ef6ff29450cf1b9d16fdfa6030.jpg\",\n            \"http://imgsrc.baidu.com/forum/w%3D580/sign=0ca60645e7cd7b89e96c3a8b3f254291/5562f6246b600c335fe5d8471b4c510fd8f9a1a6.jpg\",\n            \"http://imgsrc.baidu.com/forum/w%3D580/sign=e4a3094c3b87e9504217f3642039531b/05c69f3df8dcd100e802feae738b4710b8122fa7.jpg\",\n            \"http://imgsrc.baidu.com/forum/w%3D580/sign=09c6e2b6b2de9c82a665f9875c8080d2/8d1ab051f8198618ade4e90b4bed2e738ad4e69b.jpg\",\n            \"http://imgsrc.baidu.com/forum/w%3D580/sign=de973a9cdc54564ee565e43183df9cde/c802738da977391224d883eef9198618377ae240.jpg\",\n            \"http://imgsrc.baidu.com/forum/w%3D580/sign=c99a6a3db3b7d0a27bc90495fbee760d/431fd21b0ef41bd5c9c0ee7b50da81cb38db3daa.jpg\",\n            \"http://imgsrc.baidu.com/forum/w%3D580/sign=0a5839c403087bf47dec57e1c2d1575e/71c3d5628535e5ddf0dca35077c6a7efcf1b6249.jpg\",\n            \"http://imgsrc.baidu.com/forum/w%3D580/sign=0a2539c403087bf47dec57e1c2d2575e/71c3d5628535e5ddf0a1a35077c6a7efcf1b62b4.jpg\",\n            \"http://imgsrc.baidu.com/forum/w%3D580/sign=b7c51e4ae1fe9925cb0c695804aa5ee4/8d18ebc4b74543a90fcafc431f178a82b8011468.jpg\",\n            \"http://imgsrc.baidu.com/forum/w%3D580/sign=44bc3f4cd6ca7bcb7d7bc7278e086b3f/ac59d109b3de9c82e7eaff006d81800a18d843b6.jpg\",\n            \"http://imgsrc.baidu.com/forum/w%3D580/sign=1282e0808694a4c20a23e7233ef51bac/67ef3d6d55fbb2fb871c599d4e4a20a44623dc0f.jpg\",\n            \"http://imgsrc.baidu.com/forum/w%3D580/sign=4ef8e2ac72f082022d9291377bfafb8a/1b2cd42a2834349be088680ac8ea15ce37d3beb0.jpg\",\n            \"http://imgsrc.baidu.com/forum/w%3D580/sign=40f9e7b834fae6cd0cb4ab693fb10f9e/80086b63f6246b60dbe1b4fbeaf81a4c500fa257.jpg\",\n            \"http://imgsrc.baidu.com/forum/w%3D580/sign=44a565f19358d109c4e3a9bae15accd0/97763912b31bb05161e8b5a7377adab44bede076.jpg\",\n            \"http://imgsrc.baidu.com/forum/w%3D580/sign=241cfda49c16fdfad86cc6e6848d8cea/9a0e4bfbfbedab6480bc4b4ff636afc378311e77.jpg\",\n            \"http://imgsrc.baidu.com/forum/w%3D580/sign=10b25666f3d3572c66e29cd4ba126352/d91090ef76c6a7efd478a9a9fcfaaf51f2de66e7.jpg\",\n            \"http://imgsrc.baidu.com/forum/w%3D580/sign=93ac33b729381f309e198da199004c67/0700213fb80e7bec5964026e2e2eb9389a506b87.jpg\",\n            \"http://imgsrc.baidu.com/forum/w%3D580/sign=032a3d9baa18972ba33a00c2d6cc7b9d/45ca0a46f21fbe09b85fc29b6a600c338744ad39.jpg\",\n            \"http://imgsrc.baidu.com/forum/w%3D580/sign=970251870dd79123e0e0947c9d355917/c2029245d688d43fda16907d7c1ed21b0ff43b86.jpg\",\n            \"http://imgsrc.baidu.com/forum/w%3D580/sign=1d3b36b77af40ad115e4c7eb672d1151/eb638535e5dde71187af4279a6efce1b9c1661c4.jpg\",\n            \"http://imgsrc.baidu.com/forum/w%3D580/sign=4184e0b834fae6cd0cb4ab693fb20f9e/80086b63f6246b60da9cb3fbeaf81a4c500fa2b4.jpg\",\n            \"http://imgsrc.baidu.com/forum/w%3D580/sign=2e0d6582279759ee4a5060c382f9434e/ce1e3a292df5e0febfd2888e5d6034a85fdf7273.jpg\",\n            \"http://imgsrc.baidu.com/forum/w%3D580/sign=a5bb6b608cb1cb133e693c1bed5556da/20168a82b9014a9067104632a8773912b31bee10.jpg\",\n            \"http://imgsrc.baidu.com/forum/w%3D580/sign=973351870dd79123e0e0947c9d355917/c2029245d688d43fda27907d7c1ed21b0ff43bb7.jpg\",\n            \"http://imgsrc.baidu.com/forum/w%3D580/sign=1e04c845ae51f3dec3b2b96ca4eff0ec/c5ecab64034f78f074249a7c78310a55b3191c16.jpg\",\n            \"http://imgsrc.baidu.com/forum/w%3D580/sign=0897e5b6b2de9c82a665f9875c8380d2/8d1ab051f8198618acb5ee0b4bed2e738ad4e654.jpg\",\n            \"http://imgsrc.baidu.com/forum/w%3D580/sign=832de5a90824ab18e016e13f05fbe69a/e9cb7bcb0a46f21f6f425edcf7246b600d33aec8.jpg\",\n            \"http://imgsrc.baidu.com/forum/w%3D580/sign=ccfdb80eca1349547e1ee86c664f92dd/b483b9014a90f603e1af9cc83812b31bb051ed27.jpg\",\n            \"http://imgsrc.baidu.com/forum/w%3D580/sign=99b676a96c224f4a5799731b39f69044/626134a85edf8db1e05e7ceb0823dd54574e74c9.jpg\",\n            \"http://imgsrc.baidu.com/forum/w%3D580/sign=4f071acc8ad4b31cf03c94b3b7d4276f/48084b36acaf2edda62648668c1001e93801936e.jpg\",\n            \"http://imgsrc.baidu.com/forum/w%3D580/sign=1e71848a32fa828bd1239debcd1e41cd/8d1d8701a18b87d68530ab90060828381e30fdd4.jpg\",\n            \"http://imgsrc.baidu.com/forum/w%3D580/sign=0ff5c8a04034970a47731027a5cbd1c0/a02e070828381f304e028d26a8014c086e06f023.jpg\",\n            \"http://imgsrc.baidu.com/forum/w%3D580/sign=45c476168326cffc692abfba89004a7d/6d42fbf2b211931342ffff3f64380cd790238d86.jpg\",\n            \"http://imgsrc.baidu.com/forum/w%3D580/sign=789dc76c562c11dfded1bf2b53266255/aeee76c6a7efce1b8752c845ae51f3deb58f65c0.jpg\",\n            \"http://imgsrc.baidu.com/forum/w%3D580/sign=83e7e5a90824ab18e016e13f05fbe69a/e9cb7bcb0a46f21f6f885edcf7246b600d33ae86.jpg\",\n            \"http://imgsrc.baidu.com/forum/w%3D580/sign=8305e5a90824ab18e016e13f05fbe69a/e9cb7bcb0a46f21f6f6a5edcf7246b600d33aee0.jpg\",\n            \"http://imgsrc.baidu.com/forum/w%3D580/sign=76863c4891529822053339cbe7cb7b3b/77550923dd54564ea2fde4b6b2de9c82d0584fa1.jpg\",\n            \"http://imgsrc.baidu.com/forum/w%3D580/sign=bba379b76f061d957d4637304bf50a5d/112fb9389b504fc2c7c0b08ae4dde71191ef6da6.jpg\",\n            \"http://imgsrc.baidu.com/forum/w%3D580/sign=632a3f6e63d9f2d3201124e799ee8a53/dbdce71190ef76c6bf31fba49c16fdfaae51675a.jpg\",\n            \"http://imgsrc.baidu.com/forum/w%3D580/sign=b36fc3bc728da9774e2f86238053f872/32d6912397dda14454cb6c3db3b7d0a20df48604.jpg\",\n            \"http://imgsrc.baidu.com/forum/w%3D580/sign=7ac345703ac79f3d8fe1e4388aa3cdbc/45f50ad162d9f2d3e4d22460a8ec8a136227cc7b.jpg\",\n            \"http://imgsrc.baidu.com/forum/w%3D580/sign=92eb32b729381f309e198da199004c67/0700213fb80e7bec5823036e2e2eb9389a506b40.jpg\",\n            \"http://imgsrc.baidu.com/forum/w%3D580/sign=8ef485a38601a18bf0eb1247ae2e0761/92ae2edda3cc7cd9883037563801213fb80e9124.jpg\",\n            \"http://imgsrc.baidu.com/forum/w%3D580/sign=f9e28d26a8014c08193b28ad3a7a025b/6ae636d12f2eb9388b967a7dd4628535e4dd6f42.jpg\",\n            \"http://imgsrc.baidu.com/forum/w%3D580/sign=713a8d308d5494ee87220f111df7e0e1/46f1f736afc3793188b18aa6eac4b74542a91160.jpg\",\n            \"http://imgsrc.baidu.com/forum/w%3D580/sign=2f95b26bb21c8701d6b6b2ee177d9e6e/7537acaf2edda3cc7d3fb4af00e93901203f9262.jpg\",\n            \"http://imgsrc.baidu.com/forum/w%3D580/sign=91fa5a9863d0f703e6b295d438f85148/c3fbaf51f3deb48f97bdad51f11f3a292cf5786d.jpg\",\n            \"http://imgsrc.baidu.com/forum/w%3D580/sign=8db7ac75d0c8a786be2a4a065708c9c7/8698a9014c086e06b53d33c403087bf40bd1cbad.jpg\",\n            \"http://imgsrc.baidu.com/forum/w%3D580/sign=2474a53dd058ccbf1bbcb53229d9bcd4/c6188618367adab4dd4717cc8ad4b31c8601e4af.jpg\",\n            \"http://imgsrc.baidu.com/forum/w%3D580/sign=f3d28c425366d0167e199e20a72ad498/4c0f0cf3d7ca7bcbc04fc8a0bf096b63f624a80e.jpg\",\n            \"http://imgsrc.baidu.com/forum/w%3D580/sign=21341b5837d12f2ece05ae687fc0d5ff/45889e510fb30f24187f968cc995d143ac4b035c.jpg\",\n            \"http://imgsrc.baidu.com/forum/w%3D580/sign=cef24af3500fd9f9a0175561152cd42b/bc8aa61ea8d3fd1f643ec7b5314e251f95ca5f0e.jpg\",\n            \"http://imgsrc.baidu.com/forum/w%3D580/sign=c575a361b58f8c54e3d3c5270a282dee/3d4e78f0f736afc313155aeab219ebc4b7451220.jpg\",\n            \"http://imgsrc.baidu.com/forum/w%3D580/sign=1d9781e7cdbf6c81f7372ce08c3fb1d7/b819367adab44aedaf66db6bb21c8701a18bfb12.jpg\",\n            \"http://imgsrc.baidu.com/forum/w%3D580/sign=64c6a7bc908fa0ec7fc764051696594a/cddfb48f8c5494eef6096a962cf5e0fe99257e12.jpg\",\n            \"http://imgsrc.baidu.com/forum/w%3D580/sign=cec44af3500fd9f9a0175561152cd42b/bc8aa61ea8d3fd1f6408c7b5314e251f95ca5f38.jpg\",\n            \"http://imgsrc.baidu.com/forum/w%3D580/sign=b509d58ae4dde711e7d243fe97eecef4/ef42ad4bd11373f0e2690a10a50f4bfbfaed04ef.jpg\",\n            \"http://imgsrc.baidu.com/forum/w%3D580/sign=fc947e62a2cc7cd9fa2d34d109002104/88fc5266d0160924fb23c794d50735fae6cd343f.jpg\",\n            \"http://imgsrc.baidu.com/forum/w%3D580/sign=e9d56b789e3df8dca63d8f99fd1072bf/34d062d9f2d3572c88c5f9538b13632762d0c31f.jpg\",\n            \"http://imgsrc.baidu.com/forum/w%3D580/sign=4f2fd769267f9e2f70351d002f31e962/42d88d1001e93901cf9be8b17aec54e737d196a1.jpg\",\n            \"http://imgsrc.baidu.com/forum/w%3D580/sign=0d17b9ef342ac65c6705667bcbf3b21d/c6ddd100baa1cd11451b17afb812c8fcc2ce2d94.jpg\",\n            \"http://imgsrc.baidu.com/forum/w%3D580/sign=1cafe826a8014c08193b28ad3a7a025b/6ae636d12f2eb9386edb1f7dd4628535e4dd6f88.jpg\",\n            \"http://imgsrc.baidu.com/forum/w%3D580/sign=396a6345838ba61edfeec827713597cc/b900a18b87d6277f167057b729381f30e824fce4.jpg\",\n            \"http://imgsrc.baidu.com/forum/w%3D580/sign=e9666b789e3df8dca63d8f99fd1072bf/34d062d9f2d3572c8876f9538b13632763d0c3ae.jpg\",\n            \"http://imgsrc.baidu.com/forum/w%3D580/sign=535d594891529822053339cbe7cb7b3b/77550923dd54564e872681b6b2de9c82d0584ffa.jpg\",\n            \"http://imgsrc.baidu.com/forum/w%3D580/sign=f839457137d3d539c13d0fcb0a85e927/413f6709c93d70cf0a14cf82f9dcd100bba12b57.jpg\",\n            \"http://imgsrc.baidu.com/forum/w%3D580/sign=8cc7c3acb17eca80120539efa1219712/f6fdc3cec3fdfc03ac938637d53f8794a5c22652.jpg\",\n            \"http://imgsrc.baidu.com/forum/w%3D580/sign=d20fc662a044ad342ebf878fe0a30c08/ea3e8794a4c27d1e91375f4b1ad5ad6eddc43828.jpg\",\n            \"http://imgsrc.baidu.com/forum/w%3D580/sign=ef8ad1b518d8bc3ec60806c2b28aa6c8/74ec2e738bd4b31c040af03486d6277f9e2ff808.jpg\",\n            \"http://imgsrc.baidu.com/forum/w%3D580/sign=2fc6d7fbeaf81a4c2632ecc1e7286029/8f3433fa828ba61e8b26ada04034970a314e5971.jpg\",\n            \"http://imgsrc.baidu.com/forum/w%3D580/sign=2424ada04034970a47731027a5c8d1c0/a02e070828381f3065d3e826a8014c086f06f07c.jpg\",\n            \"http://imgsrc.baidu.com/forum/w%3D580/sign=191361f1241f95caa6f592bef9167fc5/2131e924b899a901c2113fb91c950a7b0208f51e.jpg\",\n            \"http://imgsrc.baidu.com/forum/w%3D580/sign=99ade5308d5494ee87220f111df4e0e1/46f1f736afc379316026e2a6eac4b74542a911f7.jpg\",\n            \"http://imgsrc.baidu.com/forum/w%3D580/sign=36ae6245838ba61edfeec827713597cc/b900a18b87d6277f19b456b729381f30e824fc98.jpg\",\n            \"http://imgsrc.baidu.com/forum/w%3D580/sign=198961f1241f95caa6f592bef9167fc5/2131e924b899a901c28b3fb91c950a7b0308f580.jpg\",\n            \"http://imgsrc.baidu.com/forum/w%3D580/sign=c7b8da6bb21c8701d6b6b2ee177d9e6e/7537acaf2edda3cc9512dcaf00e93901203f9248.jpg\",\n            \"http://imgsrc.baidu.com/forum/w%3D580/sign=92adf6b04afbfbeddc59367748f1f78e/3d3a5bb5c9ea15cefed18786b7003af33a87b207.jpg\",\n            \"http://imgsrc.baidu.com/forum/w%3D580/sign=2d41c0e7d009b3deebbfe460fcbe6cd3/0713b31bb051f819fd1764c5dbb44aed2e73e714.jpg\",\n            \"http://imgsrc.baidu.com/forum/w%3D580/sign=45650810a50f4bfb8cd09e5c334e788f/0a9a033b5bb5c9ead2a9646cd439b6003bf3b3ec.jpg\",\n            \"http://imgsrc.baidu.com/forum/w%3D580/sign=7d471df521a446237ecaa56aa8237246/60de8db1cb13495419468feb574e9258d0094ae1.jpg\",\n            \"http://imgsrc.baidu.com/forum/w%3D580/sign=628e11168326cffc692abfba89034a7d/6d42fbf2b211931365b5983f64380cd790238d48.jpg\",\n            \"http://imgsrc.baidu.com/forum/w%3D580/sign=54ec7f4ae1fe9925cb0c695804a95ee4/8d18ebc4b74543a9ece39d431f178a82b8011441.jpg\",\n            \"http://imgsrc.baidu.com/forum/w%3D580/sign=02244a67bd3eb13544c7b7b3961fa8cb/10728bd4b31c870123a5d469267f9e2f0708ff2b.jpg\",\n            \"http://imgsrc.baidu.com/forum/w%3D580/sign=edc5d3b518d8bc3ec60806c2b28aa6c8/74ec2e738bd4b31c0645f23486d6277f9f2ff8c1.jpg\",\n            \"http://imgsrc.baidu.com/forum/w%3D580/sign=19eb9b2ad043ad4ba62e46c8b2005a89/e7f8d72a6059252d14f27b8b359b033b5ab5b95d.jpg\",\n            \"http://imgsrc.baidu.com/forum/w%3D580/sign=13a6fef4d01373f0f53f6f97940d4b8b/5e58252dd42a2834f67e50845ab5c9ea14cebf62.jpg\",\n            \"http://imgsrc.baidu.com/forum/w%3D580/sign=003b92e3113853438ccf8729a312b01f/84a0cd11728b47108c039c43c2cec3fdfc032315.jpg\",\n            \"http://imgsrc.baidu.com/forum/w%3D580/sign=2c26deaf00e9390156028d364bee54f9/3725ab18972bd4076505b5727a899e510eb3097b.jpg\",\n            \"http://imgsrc.baidu.com/forum/w%3D580/sign=da26e7a6eac4b7453494b71efffd1e78/0b2bc65c103853432b81e6ae9213b07ecb8088f0.jpg\",\n            \"http://imgsrc.baidu.com/forum/w%3D580/sign=734253b729381f309e198da199004c67/0700213fb80e7becb98a626e2e2eb9389a506bea.jpg\",\n            \"http://imgsrc.baidu.com/forum/w%3D580/sign=e5cbc051f11f3a295ac8d5c6a924bce3/91c279310a55b319825be3fa42a98226cefc179b.jpg\",\n            \"http://imgsrc.baidu.com/forum/w%3D580/sign=289f0f0ac8ea15ce41eee00186013a25/9987c9177f3e6709e29924703ac79f3df9dc55a0.jpg\",\n            \"http://imgsrc.baidu.com/forum/w%3D580/sign=b6c55eeab219ebc4c0787691b224cf79/d751352ac65c10389cb7af4db3119313b17e8971.jpg\",\n            \"http://imgsrc.baidu.com/forum/w%3D580/sign=613a7b91b8389b5038ffe05ab537e5f1/31b20f2442a7d9339f7e85fcac4bd11372f0016f.jpg\",\n            \"http://imgsrc.baidu.com/forum/w%3D580/sign=019f897b50da81cb4ee683c56264d0a4/782209f790529822deff584cd6ca7bcb0b46d476.jpg\",\n            \"http://imgsrc.baidu.com/forum/w%3D580/sign=6c895ff9f31fbe091c5ec31c5b610c30/a283d158ccbf6c8197484c67bd3eb13532fa40c6.jpg\",\n            \"http://imgsrc.baidu.com/forum/w%3D580/sign=c67f4a8fe824b899de3c79305e071d59/860f7bec54e736d1c169ec879a504fc2d46269cc.jpg\",\n            \"http://imgsrc.baidu.com/forum/w%3D580/sign=07f5f3a1a9d3fd1f3609a232004f25ce/b9d7277f9e2f0708ad464a8fe824b899a801f2fb.jpg\",\n            \"http://imgsrc.baidu.com/forum/w%3D580/sign=b3a71718d833c895a67e9873e1127397/244d510fd9f9d72adcda7092d52a2834359bbb80.jpg\",\n            \"http://imgsrc.baidu.com/forum/w%3D580/sign=e3a385a90824ab18e016e13f05fbe69a/e9cb7bcb0a46f21f0fcc3edcf7246b600d33ae42.jpg\",\n            \"http://imgsrc.baidu.com/forum/w%3D580/sign=442c08608cb1cb133e693c1bed5556da/20168a82b9014a9086872532a8773912b21bee81.jpg\",\n            \"http://imgsrc.baidu.com/forum/w%3D580/sign=b49d5ceab219ebc4c0787691b227cf79/d751352ac65c10389eefad4db3119313b17e89a9.jpg\",\n            \"http://imgsrc.baidu.com/forum/w%3D580/sign=7caebd7bb64543a9f51bfac42e168a7b/f85d10385343fbf27da6c5acb17eca8064388fbc.jpg\",\n            \"http://imgsrc.baidu.com/forum/w%3D580/sign=2a9c0d0ac8ea15ce41eee00186013a25/9987c9177f3e6709e09a26703ac79f3df9dc55a3.jpg\",\n            \"http://imgsrc.baidu.com/forum/w%3D580/sign=ffbc54b77af40ad115e4c7eb672d1151/eb638535e5dde71165282079a6efce1b9c16614c.jpg\",\n            \"http://imgsrc.baidu.com/forum/w%3D580/sign=25e305fcfaf2b211e42e8546fa826511/4c8a4710b912c8fcf6159542fd039245d788215f.jpg\",\n            \"http://imgsrc.baidu.com/forum/w%3D580/sign=dd2feeadc9fcc3ceb4c0c93ba244d6b7/5cd1f703918fa0ec92c50682279759ee3c6ddbc4.jpg\",\n            \"http://imgsrc.baidu.com/forum/w%3D580/sign=3dc1fcdb024f78f0800b9afb49300a83/2bcf36d3d539b600fcdf6d38e850352ac65cb729.jpg\",\n            \"http://imgsrc.baidu.com/forum/w%3D580/sign=cdc3dc6bb21c8701d6b6b2ee177e9e6e/7537acaf2edda3cc9f69daaf00e93901203f92b5.jpg\",\n            \"http://imgsrc.baidu.com/forum/w%3D580/sign=227eff6cfc1f4134e0370576151e95c1/197e9e2f070828389b50be9bb999a9014d08f1e5.jpg\",\n            \"http://imgsrc.baidu.com/forum/w%3D580/sign=c796e4a6eac4b7453494b71efffd1e78/0b2bc65c103853433631e5ae9213b07ecb808840.jpg\",\n            \"http://imgsrc.baidu.com/forum/w%3D580/sign=73846eadb21bb0518f24b3200678da77/9f45ad345982b2b70a515b4b30adcbef77099b70.jpg\",\n            \"http://imgsrc.baidu.com/forum/w%3D580/sign=6e18e0b17aec54e741ec1a1689399bfd/0bfbe6cd7b899e514094579b43a7d933c8950d23.jpg\",\n            \"http://imgsrc.baidu.com/forum/w%3D580/sign=aad2f9fbac345982c58ae59a3cf5310b/b995a4c27d1ed21b33fb436aac6eddc451da3f0b.jpg\",\n            \"http://imgsrc.baidu.com/forum/w%3D580/sign=ba6411eb0823dd542173a760e108b3df/7491f603738da9774865eba4b151f8198718e3fc.jpg\",\n            \"http://imgsrc.baidu.com/forum/w%3D580/sign=a9d70ef19358d109c4e3a9bae159ccd0/97763912b31bb0518c9adea7377adab44bede080.jpg\",\n            \"http://imgsrc.baidu.com/forum/w%3D580/sign=a340ce75d0c8a786be2a4a065708c9c7/8698a9014c086e069bca51c403087bf40bd1cbe4.jpg\",\n            \"http://imgsrc.baidu.com/forum/w%3D580/sign=0801867b50da81cb4ee683c56267d0a4/782209f790529822d761574cd6ca7bcb0b46d4f4.jpg\",\n            \"http://imgsrc.baidu.com/forum/w%3D580/sign=505e9871c2fdfc03e578e3b0e43e87a9/af8ea0ec08fa513dbe3502513c6d55fbb3fbd9a7.jpg\",\n            \"http://imgsrc.baidu.com/forum/w%3D580/sign=1637b2ef342ac65c6705667bcbf3b21d/c6ddd100baa1cd115e3b1cafb812c8fcc2ce2db4.jpg\",\n            \"http://imgsrc.baidu.com/forum/w%3D580/sign=5b3f66d254fbb2fb342b581a7f482043/deff9925bc315c60367905608cb1cb1348547755.jpg\",\n            \"http://imgsrc.baidu.com/forum/w%3D580/sign=2772559cdc54564ee565e43183df9cde/c802738da9773912dd3deceef9198618367ae223.jpg\",\n            \"http://imgsrc.baidu.com/forum/w%3D580/sign=7f1ce5879a504fc2a25fb00dd5dce7f0/312542a7d933c8958171f1f4d01373f082020036.jpg\",\n            \"http://imgsrc.baidu.com/forum/w%3D580/sign=7b5c6945e7cd7b89e96c3a8b3f254291/5562f6246b600c33281fb7471b4c510fd8f9a1d8.jpg\",\n            \"http://imgsrc.baidu.com/forum/w%3D580/sign=e17b97ac48540923aa696376a259d1dc/87004a90f603738dc5bd65adb21bb051f919ec4f.jpg\",\n            \"http://imgsrc.baidu.com/forum/w%3D580/sign=4806bc419825bc312b5d01906edd8de7/d5c5b74543a98226aa0f42a88b82b9014b90eb70.jpg\",\n            \"http://imgsrc.baidu.com/forum/w%3D580/sign=999386727a899e51788e3a1c72a6d990/c8256b600c33874447917af3500fd9f9d62aa0ee.jpg\",\n            \"http://imgsrc.baidu.com/forum/w%3D580/sign=a4aaf575d0c8a786be2a4a065708c9c7/8698a9014c086e069c206ac403087bf40bd1cbb2.jpg\",\n            \"http://imgsrc.baidu.com/forum/w%3D580/sign=9f37f2acb17eca80120539efa1229712/f6fdc3cec3fdfc03bf63b737d53f8794a4c22622.jpg\",\n            \"http://imgsrc.baidu.com/forum/w%3D580/sign=97bf96bc908fa0ec7fc764051696594a/cddfb48f8c5494ee05705b962cf5e0fe98257ef4.jpg\",\n            \"http://imgsrc.baidu.com/forum/w%3D580/sign=366a5012caef76093c0b99971edca301/936fddc451da81cb0345d7425366d0160824319a.jpg\",\n            \"http://imgsrc.baidu.com/forum/w%3D580/sign=79bc4492d52a283443a636036bb7c92e/a90b304e251f95cac82dc639c8177f3e66095261.jpg\",\n            \"http://imgsrc.baidu.com/forum/w%3D580/sign=3c66365837d12f2ece05ae687fc3d5ff/45889e510fb30f24052dbb8cc995d143ac4b038e.jpg\",\n            \"http://imgsrc.baidu.com/forum/w%3D580/sign=6f3f1b8e0b55b3199cf9827d73ab8286/0486e950352ac65cd0c431fcfaf2b21192138a79.jpg\",\n            \"http://imgsrc.baidu.com/forum/w%3D580/sign=3a29f4b5314e251fe2f7e4f09787c9c2/16391f30e924b899d21c2fb76f061d950a7bf61a.jpg\",\n            \"http://imgsrc.baidu.com/forum/w%3D580/sign=c924cf8e5d6034a829e2b889fb1249d9/7da88226cffc1e17da83cabe4b90f603738de906.jpg\",\n            \"http://imgsrc.baidu.com/forum/w%3D580/sign=795ebb2ad043ad4ba62e46c8b2035a89/e7f8d72a6059252d74475b8b359b033b5ab5b9ea.jpg\",\n            \"http://imgsrc.baidu.com/forum/w%3D580/sign=8f2ec3c1cb8065387beaa41ba7dca115/fdcfc3fdfc039245cab5a7808694a4c27d1e2539.jpg\",\n            \"http://imgsrc.baidu.com/forum/w%3D580/sign=a17e796e63d9f2d3201124e799ed8a53/dbdce71190ef76c67d65bda49c16fdfaae51678f.jpg\",\n            \"http://imgsrc.baidu.com/forum/w%3D580/sign=b8d16e2a0b7b02080cc93fe952dbf25f/a5514fc2d5628535330a94ae91ef76c6a6ef635c.jpg\",\n            \"http://imgsrc.baidu.com/forum/w%3D580/sign=d8d08e45ae51f3dec3b2b96ca4eff0ec/c5ecab64034f78f0b2f0dc7c78310a55b2191c42.jpg\",\n            \"http://imgsrc.baidu.com/forum/w%3D580/sign=0ef74d64b7fd5266a72b3c1c9b199799/a623720e0cf3d7caf4f77ef9f31fbe096b63a939.jpg\",\n            \"http://imgsrc.baidu.com/forum/w%3D580/sign=8a6dc7619d82d158bb8259b9b00b19d5/8e50f8198618367a81d03e522f738bd4b21ce5e1.jpg\",\n            \"http://imgsrc.baidu.com/forum/w%3D580/sign=6242426e2e2eb938ec6d7afae56385fe/a0500fb30f2442a7ef96bc2ad043ad4bd01302a1.jpg\",\n            \"http://imgsrc.baidu.com/forum/w%3D580/sign=807c83bc908fa0ec7fc764051696594a/cddfb48f8c5494ee12b34e962cf5e0fe98257ea9.jpg\",\n            \"http://imgsrc.baidu.com/forum/w%3D580/sign=202c0f740b46f21fc9345e5bc6266b31/8ddf9c82d158ccbf9b67f4b518d8bc3eb0354163.jpg\",\n            \"http://imgsrc.baidu.com/forum/w%3D580/sign=8f8db8006d81800a6ee58906813733d6/087bdab44aed2e73d501c7a38601a18b86d6fa52.jpg\",\n            \"http://imgsrc.baidu.com/forum/w%3D580/sign=3646ba431f178a82ce3c7fa8c602737f/8c109313b07eca80d1587968902397dda04483e5.jpg\",\n            \"http://imgsrc.baidu.com/forum/w%3D580/sign=de7c8845ae51f3dec3b2b96ca4eff0ec/c5ecab64034f78f0b45cda7c78310a55b2191cee.jpg\",\n            \"http://imgsrc.baidu.com/forum/w%3D580/sign=44e28e4db3119313c743ffb855390c10/7911b912c8fcc3ced45c89bc9345d688d53f20a5.jpg\",\n            \"http://imgsrc.baidu.com/forum/w%3D580/sign=478c43c5dbb44aed594ebeec831d876a/32f531adcbef76098aa2d9102fdda3cc7dd99e91.jpg\",\n            \"http://imgsrc.baidu.com/forum/w%3D580/sign=15ed4cadb21bb0518f24b320067bda77/9f45ad345982b2b76c38794b30adcbef77099b9f.jpg\",\n            \"http://imgsrc.baidu.com/forum/w%3D580/sign=5728a4fcac4bd11304cdb73a6aaea488/e92b6059252dd42ab7894124023b5bb5c8eab8ba.jpg\",\n            \"http://imgsrc.baidu.com/forum/w%3D580/sign=3a6b438cc995d143da76e42b43f28296/6f0ed9f9d72a60595de14e952934349b023bba49.jpg\",\n            \"http://imgsrc.baidu.com/forum/w%3D580/sign=e9b556ae738b4710ce2ffdc4f3cfc3b2/97ed8a13632762d0a4170a30a1ec08fa513dc611.jpg\",\n            \"http://imgsrc.baidu.com/forum/w%3D580/sign=f37704eef2deb48ffb69a1d6c01e3aef/9565034f78f0f736a14ed28e0b55b319eac41389.jpg\",\n            \"http://imgsrc.baidu.com/forum/w%3D580/sign=d16cc5b91c950a7b75354ecc3ad0625c/87399b504fc2d56218514e62e61190ef77c66ce1.jpg\",\n            \"http://imgsrc.baidu.com/forum/w%3D580/sign=549d00a7962bd40742c7d3f54b889e9c/3447f21fbe096b63ab9dc0df0d338744eaf8acbe.jpg\",\n            \"http://imgsrc.baidu.com/forum/w%3D580/sign=1844bc875243fbf2c52ca62b807fca1e/f310728b4710b91216e46f71c2fdfc039245221d.jpg\",\n            \"http://imgsrc.baidu.com/forum/w%3D580/sign=f75d60006d81800a6ee58906813433d6/087bdab44aed2e73add11fa38601a18b87d6fa02.jpg\",\n            \"http://imgsrc.baidu.com/forum/w%3D580/sign=3ef6a04b30adcbef01347e0e9cae2e0e/25d4ad6eddc451da6f9f9564b7fd5266d11632d1.jpg\",\n            \"http://imgsrc.baidu.com/forum/w%3D580/sign=5883fc522f738bd4c421b239918a876c/f5ee76094b36acafacea15737dd98d1001e99c3c.jpg\",\n            \"http://imgsrc.baidu.com/forum/w%3D580/sign=7e7320730e2442a7ae0efdade142ad95/b945ebf81a4c510f39dbf8ea6159252dd42aa520.jpg\",\n            \"http://imgsrc.baidu.com/forum/w%3D580/sign=4f4f6c698326cffc692abfba89004a7d/6d42fbf2b21193134874e54064380cd791238d08.jpg\",\n            \"http://imgsrc.baidu.com/forum/w%3D580/sign=01637b2737d12f2ece05ae687fc3d5ff/45889e510fb30f243828f6f3c995d143ac4b0394.jpg\",\n            \"http://imgsrc.baidu.com/forum/w%3D580/sign=c66aa271ca1349547e1ee86c664f92dd/b483b9014a90f603eb3886b73812b31bb151edb3.jpg\",\n            \"http://imgsrc.baidu.com/forum/w%3D580/sign=99e807f4359b033b2c88fcd225cf3620/1b1e95cad1c8a78684d550fe6609c93d71cf5047.jpg\",\n            \"http://imgsrc.baidu.com/forum/w%3D580/sign=0521d2df4034970a47731027a5c8d1c0/a02e070828381f3044d69759a8014c086f06f070.jpg\",\n            \"http://imgsrc.baidu.com/forum/w%3D580/sign=0e58043ae7cd7b89e96c3a8b3f254291/5562f6246b600c335d1bda381b4c510fd8f9a1e5.jpg\",\n            \"http://imgsrc.baidu.com/forum/w%3D580/sign=6c025fe24e4a20a4311e3ccfa0539847/0aa95edf8db1cb1366403be3dc54564e92584b11.jpg\",\n            \"http://imgsrc.baidu.com/forum/w%3D580/sign=6de78b61cc11728b302d8c2af8fec3b3/2fdea9ec8a136327de37c6c3908fa0ec09fac76d.jpg\",\n            \"http://imgsrc.baidu.com/forum/w%3D580/sign=3eaebe9495eef01f4d1418cdd0ff99e0/c937afc379310a553938d904b64543a982261026.jpg\",\n            \"http://imgsrc.baidu.com/forum/w%3D580/sign=a578aae2730e0cf3a0f74ef33a47f23d/e355564e9258d10986f4ae42d058ccbf6c814d2f.jpg\",\n            \"http://imgsrc.baidu.com/forum/w%3D580/sign=1751013a838ba61edfeec827713597cc/b900a18b87d6277f384b35c829381f30e824fce2.jpg\",\n            \"http://imgsrc.baidu.com/forum/w%3D580/sign=073ea62ef11f3a295ac8d5c6a924bce3/91c279310a55b31960ae858542a98226cefc17ef.jpg\",\n            \"http://imgsrc.baidu.com/forum/w%3D580/sign=11b8673bb3fb43161a1f7a7210a64642/a724bc315c6034a8720abf71ca13495408237652.jpg\",\n            \"http://imgsrc.baidu.com/forum/w%3D580/sign=45090413d439b6004dce0fbfd9513526/5908c93d70cf3bc7cffaca1cd000baa1cc112a4c.jpg\",\n            \"http://imgsrc.baidu.com/forum/w%3D580/sign=6d115e41810a19d8cb03840d03fb82c9/e4b54aed2e738bd412b2b5c1a08b87d6267ff9b4.jpg\",\n            \"http://imgsrc.baidu.com/forum/w%3D580/sign=34f8413083025aafd3327ec3cbefab8d/ea2b2834349b033bbe5efb2a14ce36d3d439bd69.jpg\",\n            \"http://imgsrc.baidu.com/forum/w%3D580/sign=01abfed1738b4710ce2ffdc4f3cfc3b2/97ed8a13632762d04c09a24fa1ec08fa513dc608.jpg\",\n            \"http://imgsrc.baidu.com/forum/w%3D580/sign=64fb026dcaef76093c0b99971edca301/936fddc451da81cb51d4853d5366d01609243114.jpg\",\n            \"http://imgsrc.baidu.com/forum/w%3D580/sign=33d7e6a28435e5dd902ca5d746c7a7f5/f694d143ad4bd1130fe5b1c25bafa40f4bfb0512.jpg\",\n            \"http://imgsrc.baidu.com/forum/w%3D580/sign=19b69c0378310a55c424defc87444387/04f23a87e950352a28dc23f85243fbf2b3118b86.jpg\",\n            \"http://imgsrc.baidu.com/forum/w%3D580/sign=8b8c14bf9f2f07085f052a08d925b865/b31101e93901213fb454482c55e736d12e2e95df.jpg\",\n            \"http://imgsrc.baidu.com/forum/w%3D580/sign=efa064ec10dfa9ecfd2e561f52d1f754/48c7a7efce1b9d16e0899c91f2deb48f8d5464f0.jpg\",\n            \"http://imgsrc.baidu.com/forum/w%3D580/sign=3bac038e241f95caa6f592bef9167fc5/2131e924b899a901e0ae5dc61c950a7b0308f5ac.jpg\",\n            \"http://imgsrc.baidu.com/forum/w%3D580/sign=d1b37167d833c895a67e9873e1127397/244d510fd9f9d72abece16edd52a2834359bbb9d.jpg\",\n            \"http://imgsrc.baidu.com/forum/w%3D580/sign=cc92a198d009b3deebbfe460fcbd6cd3/0713b31bb051f8191cc405badbb44aed2f73e75a.jpg\",\n            \"http://imgsrc.baidu.com/forum/w%3D580/sign=f80a8c59a8014c08193b28ad3a7a025b/6ae636d12f2eb9388a7e7b02d4628535e5dd6f2b.jpg\",\n            \"http://imgsrc.baidu.com/forum/w%3D580/sign=080ee5c9b2de9c82a665f9875c8080d2/8d1ab051f8198618ac2cee744bed2e738ad4e6dc.jpg\",\n            \"http://imgsrc.baidu.com/forum/w%3D580/sign=4116378f79f0f736d8fe4c093a54b382/08d2d539b6003af3d0f5dd90342ac65c1138b6f0.jpg\",\n            \"http://imgsrc.baidu.com/forum/w%3D580/sign=435a7b8a21a446237ecaa56aa8237246/60de8db1cb134954275be994574e9258d0094afd.jpg\",\n            \"http://imgsrc.baidu.com/forum/w%3D580/sign=5eb87d940823dd542173a760e108b3df/7491f603738da977acb987dbb151f8198718e3a9.jpg\",\n            \"http://imgsrc.baidu.com/forum/w%3D580/sign=0b081ef81e30e924cfa49c397c096e66/1f3eb80e7bec54e73ad119eeb8389b504fc26a05.jpg\",\n            \"http://imgsrc.baidu.com/forum/w%3D580/sign=1c7d9da4024f78f0800b9afb49300a83/2bcf36d3d539b600dd630c47e850352ac75cb796.jpg\",\n            \"http://imgsrc.baidu.com/forum/w%3D580/sign=ccad6f42b3b7d0a27bc90495fbee760d/431fd21b0ef41bd5ccf7eb0450da81cb38db3d9e.jpg\",\n            \"http://imgsrc.baidu.com/forum/w%3D580/sign=edf04106a6efce1bea2bc8c29f50f3e8/bc035aafa40f4bfb86f29ca4024f78f0f7361824.jpg\",\n            \"http://imgsrc.baidu.com/forum/w%3D580/sign=d03350c9c83d70cf4cfaaa05c8ddd1ba/347a02087bf40ad1cdd4c513562c11dfa9ecce0b.jpg\",\n            \"http://imgsrc.baidu.com/forum/w%3D580/sign=44ec940c7dd98d1076d40c39113eb807/6c67d0160924ab18fd1ae3c734fae6cd7b890b36.jpg\",\n            \"http://imgsrc.baidu.com/forum/w%3D580/sign=03ac6e2e3c6d55fbc5c6762e5d234f40/13f4e0fe9925bc31795b03685fdf8db1cb137038.jpg\",\n            \"http://imgsrc.baidu.com/forum/w%3D580/sign=5e9a0113d439b6004dce0fbfd9513526/5908c93d70cf3bc7d469cf1cd000baa1cc112ad1.jpg\",\n            \"http://imgsrc.baidu.com/forum/w%3D580/sign=5a578ef89a504fc2a25fb00dd5dce7f0/312542a7d933c895a43a9a8bd01373f0830200fe.jpg\",\n            \"http://imgsrc.baidu.com/forum/w%3D580/sign=c9c04c2c55e736d158138c00ab524ffc/d8cc7b899e510fb37eea7567d833c895d0430c4b.jpg\",\n            \"http://imgsrc.baidu.com/forum/w%3D580/sign=15e18bf15d6034a829e2b889fb1249d9/7da88226cffc1e1706468ec14b90f603728de942.jpg\",\n            \"http://imgsrc.baidu.com/forum/w%3D580/sign=862f8bfeb03533faf5b6932698d1fdca/b5d5b31c8701a18b182110bf9f2f07082938fe7d.jpg\",\n            \"http://imgsrc.baidu.com/forum/w%3D580/sign=05b9e6c9b2de9c82a665f9875c8380d2/8d1ab051f8198618a19bed744bed2e738ad4e667.jpg\",\n            \"http://imgsrc.baidu.com/forum/w%3D580/sign=c42488becb8065387beaa41ba7dca115/fdcfc3fdfc03924581bfecff8694a4c27d1e253c.jpg\",\n            \"http://imgsrc.baidu.com/forum/w%3D580/sign=234f89f532fa828bd1239debcd1e41cd/8d1d8701a18b87d6b80ea6ef060828381e30fdf7.jpg\",\n            \"http://imgsrc.baidu.com/forum/w%3D580/sign=90e810f4359b033b2c88fcd225cf3620/1b1e95cad1c8a7868dd547fe6609c93d71cf5047.jpg\",\n            \"http://imgsrc.baidu.com/forum/w%3D580/sign=82d9994b86d6277fe9123230183a1f63/9dcd7cd98d1001e93cd919ffb90e7bec55e7975d.jpg\",\n            \"http://imgsrc.baidu.com/forum/w%3D580/sign=0989ac2ef11f3a295ac8d5c6a927bce3/91c279310a55b3196e198f8542a98226cefc175a.jpg\",\n            \"http://imgsrc.baidu.com/forum/w%3D580/sign=c8ebf1fd71cf3bc7e800cde4e102babd/3c097bf40ad162d95d316fec10dfa9ec8b13cd60.jpg\",\n            \"http://imgsrc.baidu.com/forum/w%3D580/sign=73c58b3b42166d223877159c76220945/82305c6034a85edfab8ff2d348540923dd54753b.jpg\",\n            \"http://imgsrc.baidu.com/forum/w%3D580/sign=44c116eeb8389b5038ffe05ab534e5f1/31b20f2442a7d933ba85e883ac4bd11373f00115.jpg\",\n            \"http://imgsrc.baidu.com/forum/w%3D580/sign=04f9e898cdbf6c81f7372ce08c3fb1d7/b819367adab44aedb608b214b21c8701a08bfbf9.jpg\",\n            \"http://imgsrc.baidu.com/forum/w%3D580/sign=69eeedff8694a4c20a23e7233ef51bac/67ef3d6d55fbb2fbfc7054e24e4a20a44723dcec.jpg\",\n            \"http://imgsrc.baidu.com/forum/w%3D580/sign=7f22c5c39345d688a302b2ac94c07dab/36fb513d269759ee81b26c3bb3fb43166c22df65.jpg\",\n            \"http://imgsrc.baidu.com/forum/w%3D580/sign=845a90b73812b31bc76ccd21b61a3674/a9dca144ad3459824560e9620df431adcaef845d.jpg\",\n            \"http://imgsrc.baidu.com/forum/w%3D580/sign=20721fedd52a283443a636036bb4c92e/a90b304e251f95ca91e39d46c8177f3e67095228.jpg\",\n            \"http://imgsrc.baidu.com/forum/w%3D580/sign=8fdb373430adcbef01347e0e9cae2e0e/25d4ad6eddc451dadeb2021bb7fd5266d11632fe.jpg\",\n            \"http://imgsrc.baidu.com/forum/w%3D580/sign=cbe8f2fd71cf3bc7e800cde4e102babd/3c097bf40ad162d95e326cec10dfa9ec8b13cd67.jpg\",\n            \"http://imgsrc.baidu.com/forum/w%3D580/sign=06af9313fc1f4134e0370576151e95c1/197e9e2f07082838bf81d2e4b999a9014d08f1b7.jpg\",\n            \"http://imgsrc.baidu.com/forum/w%3D580/sign=392a9413fc1f4134e0370576151e95c1/197e9e2f070828388004d5e4b999a9014c08f132.jpg\",\n            \"http://imgsrc.baidu.com/forum/w%3D580/sign=343240499f510fb37819779fe931c893/55610c338744ebf8c8b36acfd8f9d72a6159a705.jpg\",\n            \"http://imgsrc.baidu.com/forum/w%3D580/sign=9527a3fdf9dcd100cd9cf829428947be/5cd8f2d3572c11df070cb6d3622762d0f603c266.jpg\",\n            \"http://imgsrc.baidu.com/forum/w%3D580/sign=846698cf4afbfbeddc59367748f1f78e/3d3a5bb5c9ea15cee81ae9f9b7003af33b87b24f.jpg\",\n            \"http://imgsrc.baidu.com/forum/w%3D580/sign=01fa14f81e30e924cfa49c397c0a6e66/1f3eb80e7bec54e7302313eeb8389b504ec26a77.jpg\",\n            \"http://imgsrc.baidu.com/forum/w%3D580/sign=5781ecc0bba1cd1105b672288913c8b0/692d11dfa9ec8a13cc7a5f10f603918fa1ecc0db.jpg\",\n            \"http://imgsrc.baidu.com/forum/w%3D580/sign=811b9291f2deb48ffb69a1d6c01d3aef/9565034f78f0f736d32244f10b55b319eac41366.jpg\",\n            // repeat case.\n            \"http://imgsrc.baidu.com/forum/w%3D580/sign=44c116eeb8389b5038ffe05ab534e5f1/31b20f2442a7d933ba85e883ac4bd11373f00115.jpg\",\n            \"http://imgsrc.baidu.com/forum/w%3D580/sign=d383b614b21c8701d6b6b2ee177d9e6e/7537acaf2edda3cc8129b0d000e93901203f9276.jpg\",\n            \"http://imgsrc.baidu.com/forum/w%3D580/sign=44e9071bb7fd5266a72b3c1c9b199799/a623720e0cf3d7cabee93486f31fbe096b63a920.jpg\",\n            \"http://imgsrc.baidu.com/forum/w%3D580/sign=abefa0910eb30f24359aec0bf894d192/32328744ebf81a4c8169e739d62a6059242da6ec.jpg\",\n            \"http://imgsrc.baidu.com/forum/w%3D580/sign=59d35241810a19d8cb03840d03fb82c9/e4b54aed2e738bd42670b9c1a08b87d6267ff9ea.jpg\",\n            \"http://imgsrc.baidu.com/forum/w%3D580/sign=484d0f940823dd542173a760e108b3df/7491f603738da977ba4cf5dbb151f8198718e3e4.jpg\",\n            \"http://imgsrc.baidu.com/forum/w%3D580/sign=54d963edd52a283443a636036bb4c92e/a90b304e251f95cae548e146c8177f3e66095285.jpg\",\n            \"http://imgsrc.baidu.com/forum/w%3D580/sign=5cf0571fa8ec8a13141a57e8c7019157/99eece1b9d16fdfaa48db51eb58f8c5495ee7b59.jpg\",\n            \"http://imgsrc.baidu.com/forum/w%3D580/sign=6e9d843dfd039245a1b5e107b795a4a8/9eed08fa513d2697f4bd7aad54fbb2fb4216d8d1.jpg\",\n            \"http://imgsrc.baidu.com/forum/w%3D580/sign=197d132737d12f2ece05ae687fc3d5ff/45889e510fb30f2420369ef3c995d143ac4b0396.jpg\",\n            \"http://imgsrc.baidu.com/forum/w%3D580/sign=6643c054a5c27d1ea5263bcc2bd4adaf/036c55fbb2fb4316df5e088a21a4462308f7d3fa.jpg\",\n            \"http://imgsrc.baidu.com/forum/w%3D580/sign=43ad6a35e1fe9925cb0c695804a95ee4/8d18ebc4b74543a9fba2883c1f178a82b8011481.jpg\",\n            \"http://imgsrc.baidu.com/forum/w%3D580/sign=2f54900a7acb0a4685228b315b62f63e/ef08b3de9c82d158ef182b41810a19d8bd3e42ac.jpg\",\n            \"http://imgsrc.baidu.com/forum/w%3D580/sign=1303803cc2cec3fd8b3ea77de689d4b6/c902918fa0ec08fafb2c6e5758ee3d6d55fbda17.jpg\",\n            \"http://imgsrc.baidu.com/forum/w%3D580/sign=1c43e58bd01373f0f53f6f97940e4b8b/5e58252dd42a2834f99b4bfb5ab5c9ea15cebf06.jpg\",\n            \"http://imgsrc.baidu.com/forum/w%3D580/sign=6e1db4c39345d688a302b2ac94c07dab/36fb513d269759ee908d1d3bb3fb43166c22df66.jpg\",\n            \"http://imgsrc.baidu.com/forum/w%3D580/sign=76d930f10b55b3199cf9827d73a88286/0486e950352ac65cc9221a83faf2b21193138a18.jpg\",\n            \"http://imgsrc.baidu.com/forum/w%3D580/sign=2fe5e2a4024f78f0800b9afb49300a83/2bcf36d3d539b600eefb7347e850352ac65cb70e.jpg\",\n            // repeat case.\n            \"http://imgsrc.baidu.com/forum/w%3D580/sign=0b081ef81e30e924cfa49c397c096e66/1f3eb80e7bec54e73ad119eeb8389b504fc26a05.jpg\",\n            \"http://imgsrc.baidu.com/forum/w%3D580/sign=ef54f0d2c9fcc3ceb4c0c93ba244d6b7/5cd1f703918fa0eca0be18fd279759ee3c6ddbc2.jpg\",\n            \"http://imgsrc.baidu.com/forum/w%3D580/sign=3665fe8542a98226b8c12b2fba83b97a/2e395343fbf2b2114eb2f9becb8065380dd78ea7.jpg\",\n            \"http://imgsrc.baidu.com/forum/w%3D580/sign=82352cf80dd79123e0e0947c9d355917/c2029245d688d43fcf21ed027c1ed21b0ff43bb2.jpg\",\n            \"http://imgsrc.baidu.com/forum/w%3D580/sign=220b01f6adaf2eddd4f149e1bd110102/bfca39dbb6fd5266841443e4aa18972bd4073607.jpg\",\n            \"http://imgsrc.baidu.com/forum/w%3D580/sign=1328ccd8377adab43dd01b4bbbd5b36b/eea30cf431adcbef9d3801f6adaf2edda3cc9f37.jpg\",\n            \"http://imgsrc.baidu.com/forum/w%3D580/sign=0b1748c87af40ad115e4c7eb672d1151/eb638535e5dde71191833c06a6efce1b9c1661e9.jpg\",\n            \"http://imgsrc.baidu.com/forum/w%3D580/sign=d646f4c96a63f6241c5d390bb745eb32/f2be6c81800a19d89867fbf532fa828ba71e46de.jpg\",\n            \"http://imgsrc.baidu.com/forum/w%3D580/sign=65a86bffb90e7bec23da03e91f2fb9fa/ea0635fae6cd7b89c2f845730e2442a7d8330eae.jpg\",\n            \"http://imgsrc.baidu.com/forum/w%3D580/sign=5df6433791529822053339cbe7c87b3b/77550923dd54564e898d9bc9b2de9c82d0584f52.jpg\",\n            \"http://imgsrc.baidu.com/forum/w%3D580/sign=5ddf853c1f178a82ce3c7fa8c601737f/8c109313b07eca80bac14617902397dda044837f.jpg\",\n            \"http://imgsrc.baidu.com/forum/w%3D580/sign=840d71e92cf5e0feee1889096c6134e5/3454b319ebc4b74537bbc9e6cefc1e178a821517.jpg\",\n            \"http://imgsrc.baidu.com/forum/w%3D580/sign=11119ac9b2de9c82a665f9875c8080d2/8d1ab051f8198618b53391744bed2e738ad4e6cf.jpg\",\n            \"http://imgsrc.baidu.com/forum/w%3D580/sign=4198f93b42166d223877159c76220945/82305c6034a85edf99d280d348540923dc5475e0.jpg\",\n            \"http://imgsrc.baidu.com/forum/w%3D580/sign=220b7d13d439b6004dce0fbfd9513526/5908c93d70cf3bc7a8f8b31cd000baa1cc112a42.jpg\",\n            \"http://imgsrc.baidu.com/forum/w%3D580/sign=1ff9e76f2fdda3cc0be4b82831eb3905/07dab6fd5266d0165debe0d8962bd40734fa3554.jpg\",\n            \"http://imgsrc.baidu.com/forum/w%3D580/sign=7a33f4ce7aec54e741ec1a1689399bfd/0bfbe6cd7b899e5154bf43e443a7d933c8950d09.jpg\",\n            \"http://imgsrc.baidu.com/forum/w%3D580/sign=e5089fd60824ab18e016e13f05fbe69a/e9cb7bcb0a46f21f096724a3f7246b600d33aef4.jpg\",\n            \"http://imgsrc.baidu.com/forum/w%3D580/sign=51d122e24e4a20a4311e3ccfa0539847/0aa95edf8db1cb135b9346e3dc54564e93584b4c.jpg\",\n            \"http://imgsrc.baidu.com/forum/w%3D580/sign=1a099855d043ad4ba62e46c8b2035a89/e7f8d72a6059252d171078f4359b033b5bb5b938.jpg\",\n            \"http://imgsrc.baidu.com/forum/w%3D580/sign=b1bafbd8962bd40742c7d3f54b889e9c/3447f21fbe096b634eba3ba00d338744eaf8aca4.jpg\",\n            \"http://imgsrc.baidu.com/forum/w%3D580/sign=052385f9b7003af34dbadc680528c619/f73c70cf3bc79f3dcb2c81c0bba1cd11738b2975.jpg\",\n            \"http://imgsrc.baidu.com/forum/w%3D580/sign=b7eaa6f6aec379317d688621dbc5b784/88013af33a87e950fa30979c11385343fbf2b418.jpg\",\n            \"http://imgsrc.baidu.com/forum/w%3D580/sign=6dc5001a80cb39dbc1c0675ee01709a7/37f690529822720ebcf2860a7acb0a46f21fab07.jpg\",\n            \"http://imgsrc.baidu.com/forum/w%3D580/sign=e3f97f1da2cc7cd9fa2d34d109002104/88fc5266d0160924e44ec6ebd50735fae7cd34d3.jpg\",\n            \"http://imgsrc.baidu.com/forum/w%3D580/sign=8a1634f80dd79123e0e0947c9d355917/c2029245d688d43fc702f5027c1ed21b0ff43b93.jpg\",\n            \"http://imgsrc.baidu.com/forum/w%3D580/sign=676aadc39345d688a302b2ac94c37dab/36fb513d269759ee99fa043bb3fb43166c22df9d.jpg\",\n            \"http://imgsrc.baidu.com/forum/w%3D580/sign=5ca7230f3ac79f3d8fe1e4388aa0cdbc/45f50ad162d9f2d3c2b6421fa8ec8a136227cc98.jpg\",\n            \"http://imgsrc.baidu.com/forum/w%3D580/sign=579e7e35e1fe9925cb0c695804a95ee4/8d18ebc4b74543a9ef919c3c1f178a82b80114bc.jpg\",\n            \"http://imgsrc.baidu.com/forum/w%3D580/sign=75d38a39d62a60595210e1121836342d/96d2fd1f4134970a149276df94cad1c8a6865d59.jpg\",\n            \"http://imgsrc.baidu.com/forum/w%3D580/sign=a51b028e9358d109c4e3a9bae159ccd0/97763912b31bb0518056d2d8377adab44bede0d5.jpg\",\n            \"http://imgsrc.baidu.com/forum/w%3D580/sign=0801ac1cd000baa1ba2c47b37711b9b1/ccd2572c11dfa9ec1d2f37e763d0f703918fc13a.jpg\",\n            \"http://imgsrc.baidu.com/forum/w%3D580/sign=035a29ded31b0ef46ce89856edc551a1/8cfa43166d224f4a873c12e308f790529922d19e.jpg\",\n            \"http://imgsrc.baidu.com/forum/w%3D580/sign=ff07d50b38dbb6fd255be52e3926aba6/ae539822720e0cf379172f0b0b46f21fbf09aa5b.jpg\",\n            \"http://imgsrc.baidu.com/forum/w%3D580/sign=3229663a838ba61edfeec827713597cc/b900a18b87d6277f1d3352c829381f30e924fc1a.jpg\",\n            \"http://imgsrc.baidu.com/forum/w%3D580/sign=bb4b70df94cad1c8d0bbfc2f4f3f67c4/d725b899a9014c08e50348550b7b02087bf4f403.jpg\",\n            \"http://imgsrc.baidu.com/forum/w%3D580/sign=e9a312d0b812c8fcb4f3f6c5cc0292b4/5d2662d0f703918f3bc45d3a503d269759eec42e.jpg\",\n            \"http://imgsrc.baidu.com/forum/w%3D580/sign=76f09e2f77094b36db921be593cd7c00/e3c551da81cb39dbf1a007a6d1160924aa1830da.jpg\",\n            \"http://imgsrc.baidu.com/forum/w%3D580/sign=76e159f38644ebf86d716437e9f8d736/823fb13533fa828bbb9ffd13fc1f4134960a5a86.jpg\",\n            \"http://imgsrc.baidu.com/forum/w%3D580/sign=213e0c2e3c6d55fbc5c6762e5d234f40/13f4e0fe9925bc315bc961685fdf8db1ca1370ae.jpg\",\n            \"http://imgsrc.baidu.com/forum/w%3D580/sign=fcc657c87af40ad115e4c7eb672d1151/eb638535e5dde71166522306a6efce1b9c1661bb.jpg\",\n            \"http://imgsrc.baidu.com/forum/w%3D580/sign=75946cd2b21bb0518f24b3200678da77/9f45ad345982b2b70c41593430adcbef77099b61.jpg\",\n            \"http://imgsrc.baidu.com/forum/w%3D580/sign=6ebae5dc8601a18bf0eb1247ae2e0761/92ae2edda3cc7cd9687e57293801213fb90e91f4.jpg\",\n            \"http://imgsrc.baidu.com/forum/w%3D580/sign=f2707ef4359b033b2c88fcd225cf3620/1b1e95cad1c8a786ef4d29fe6609c93d71cf50df.jpg\",\n            \"http://imgsrc.baidu.com/forum/w%3D580/sign=b4ce1467d833c895a67e9873e1117397/244d510fd9f9d72adbb373edd52a2834359bbb68.jpg\",\n            \"http://imgsrc.baidu.com/forum/w%3D580/sign=5ee9943dfd039245a1b5e107b795a4a8/9eed08fa513d2697c4c96aad54fbb2fb4316d82d.jpg\",\n            \"http://imgsrc.baidu.com/forum/w%3D580/sign=9a4da513562c11dfded1bf2b53266255/aeee76c6a7efce1b6582aa3aae51f3deb58f6592.jpg\",\n            \"http://imgsrc.baidu.com/forum/w%3D580/sign=7da28a94574e9258a63486e6ac83d1d1/4d8ca9773912b31bc4d0afd98718367adbb4e187.jpg\",\n            \"http://imgsrc.baidu.com/forum/w%3D580/sign=ea97c32ef11f3a295ac8d5c6a924bce3/91c279310a55b3198d07e08542a98226cefc1740.jpg\",\n            \"http://imgsrc.baidu.com/forum/w%3D580/sign=2eca244da8773912c4268569c81b8675/af2297dda144ad34814be577d1a20cf430ad854f.jpg\",\n            \"http://imgsrc.baidu.com/forum/w%3D580/sign=76938cff8694a4c20a23e7233ef51bac/67ef3d6d55fbb2fbe30d35e24e4a20a44623dc19.jpg\",\n            \"http://imgsrc.baidu.com/forum/w%3D580/sign=703761d2b21bb0518f24b320067bda77/9f45ad345982b2b709e2543430adcbef77099bc6.jpg\",\n            \"http://imgsrc.baidu.com/forum/w%3D580/sign=31276c685fdf8db1bc2e7c6c3922dddb/f1fd1e178a82b90127d7aec3728da9773812efcd.jpg\",\n            \"http://imgsrc.baidu.com/forum/w%3D580/sign=63555433d6ca7bcb7d7bc7278e086b3f/ac59d109b3de9c82c003947f6d81800a18d843d8.jpg\",\n            \"http://imgsrc.baidu.com/forum/w%3D580/sign=13e989d372f082022d9291377bfafb8a/1b2cd42a2834349bbd990375c8ea15ce37d3bea0.jpg\",\n            \"http://imgsrc.baidu.com/forum/w%3D580/sign=9b6a1d1da044ad342ebf878fe0a30c08/ea3e8794a4c27d1ed85284341ad5ad6edcc438ce.jpg\",\n            \"http://imgsrc.baidu.com/forum/w%3D580/sign=bbbd7fdf3b292df597c3ac1d8c305ce2/47300a55b319ebc49816cc698326cffc1f1716d0.jpg\",\n            \"http://imgsrc.baidu.com/forum/w%3D580/sign=bfd45bff8694a4c20a23e7233ef51bac/67ef3d6d55fbb2fb2a4ae2e24e4a20a44723dcda.jpg\",\n            \"http://imgsrc.baidu.com/forum/w%3D580/sign=e5a872df4034970a47731027a5cbd1c0/a02e070828381f30a45f3759a8014c086f06f0f9.jpg\",\n            \"http://imgsrc.baidu.com/forum/w%3D580/sign=0f7726c14d086e066aa83f43320a7b5a/08d02f2eb9389b50f82c5aa28435e5dde6116e74.jpg\",\n            \"http://imgsrc.baidu.com/forum/w%3D580/sign=c21fc3a6d1160924dc25a213e405359b/32f2d7ca7bcb0a4686fd2fc96a63f6246a60af60.jpg\",\n            \"http://imgsrc.baidu.com/forum/w%3D580/sign=11c82191f91986184147ef8c7aef2e69/6783b2b7d0a20cf4937e5a2f77094b36adaf9951.jpg\",\n            \"http://imgsrc.baidu.com/forum/w%3D580/sign=078b0c910eb30f24359aec0bf894d192/32328744ebf81a4c2d0d4b39d62a6059252da600.jpg\",\n            \"http://imgsrc.baidu.com/forum/w%3D580/sign=6e3ba7685fdf8db1bc2e7c6c3922dddb/f1fd1e178a82b90178cb65c3728da9773812efd9.jpg\",\n            \"http://imgsrc.baidu.com/forum/w%3D580/sign=77603eaedcc451daf6f60ce386fc52a5/1ea5462309f79052f497e1ce0df3d7ca7acbd5b3.jpg\",\n            \"http://imgsrc.baidu.com/forum/w%3D580/sign=9ca0189495eef01f4d1418cdd0ff99e0/c937afc379310a559b367f04b64543a982261034.jpg\",\n            \"http://imgsrc.baidu.com/forum/w%3D580/sign=8da01242b8014a90813e46b599763971/8e7fca8065380cd702940f1da044ad34588281bd.jpg\",\n            \"http://imgsrc.baidu.com/forum/w%3D580/sign=6856f2a3f7246b607b0eb27cdbf91a35/5280800a19d8bc3e676aaa3a838ba61ea9d345e5.jpg\",\n            \"http://imgsrc.baidu.com/forum/w%3D580/sign=d1a82d4f8d5494ee87220f111df4e0e1/46f1f736afc3793128232ad9eac4b74542a911f3.jpg\",\n            \"http://imgsrc.baidu.com/forum/w%3D580/sign=1b68afbadbb44aed594ebeec831d876a/32f531adcbef7609d646356f2fdda3cc7dd99ef6.jpg\",\n            \"http://imgsrc.baidu.com/forum/w%3D580/sign=764614d000e9390156028d364bed54f9/3725ab18972bd4073f657f0d7a899e510eb309a4.jpg\",\n            \"http://imgsrc.baidu.com/forum/w%3D580/sign=6a85ce30f636afc30e0c3f6d831beb85/eb38b6003af33a87809a83eac75c10385243b548.jpg\",\n            \"http://imgsrc.baidu.com/forum/w%3D580/sign=ab254f1cd000baa1ba2c47b37711b9b1/ccd2572c11dfa9ecbe0bd4e763d0f703918fc11e.jpg\",\n            \"http://imgsrc.baidu.com/forum/w%3D580/sign=b124c43083025aafd3327ec3cbecab8d/ea2b2834349b033b3b827e2a14ce36d3d539bd3d.jpg\",\n            \"http://imgsrc.baidu.com/forum/w%3D580/sign=0372a20e37d3d539c13d0fcb0a86e927/413f6709c93d70cff15f28fdf9dcd100bba12b9e.jpg\",\n            \"http://imgsrc.baidu.com/forum/w%3D580/sign=5801d8f4359b033b2c88fcd225cf3620/1b1e95cad1c8a786453c8ffe6609c93d70cf5029.jpg\",\n            \"http://imgsrc.baidu.com/forum/w%3D580/sign=0a47a942b3b7d0a27bc90495fbee760d/431fd21b0ef41bd50a1d2d0450da81cb38db3df1.jpg\",\n            \"http://imgsrc.baidu.com/forum/w%3D580/sign=b801393cc2cec3fd8b3ea77de689d4b6/c902918fa0ec08fa502ed75758ee3d6d55fbda11.jpg\",\n            \"http://imgsrc.baidu.com/forum/w%3D580/sign=ad491990342ac65c6705667bcbf0b21d/c6ddd100baa1cd11e545b7d0b812c8fcc2ce2d54.jpg\",\n            \"http://imgsrc.baidu.com/forum/w%3D580/sign=59288a340bd162d985ee621421dea950/bb34e5dde71190ef2de6562fcf1b9d16fdfa6026.jpg\",\n            \"http://imgsrc.baidu.com/forum/w%3D580/sign=0695a78e9358d109c4e3a9bae159ccd0/97763912b31bb05123d877d8377adab44bede040.jpg\",\n            \"http://imgsrc.baidu.com/forum/w%3D580/sign=8af396c9c83d70cf4cfaaa05c8ded1ba/347a02087bf40ad197140313562c11dfa8ecce54.jpg\",\n            \"http://imgsrc.baidu.com/forum/w%3D580/sign=8f6b71ca18d8bc3ec60806c2b289a6c8/74ec2e738bd4b31c64eb504b86d6277f9f2ff869.jpg\",\n            \"http://imgsrc.baidu.com/forum/w%3D580/sign=eb622083ac4bd11304cdb73a6aada488/e92b6059252dd42a0bc3c55b023b5bb5c8eab87d.jpg\",\n            \"http://imgsrc.baidu.com/forum/w%3D580/sign=ad6a65ca314e251fe2f7e4f09787c9c2/16391f30e924b899455fbec86f061d950b7bf6e5.jpg\",\n            \"http://imgsrc.baidu.com/forum/w%3D580/sign=a3032f0450da81cb4ee683c56267d0a4/782209f7905298227c63fe33d6ca7bcb0b46d4eb.jpg\",\n            \"http://imgsrc.baidu.com/forum/w%3D580/sign=e5226ae2730e0cf3a0f74ef33a47f23d/e355564e9258d109c6ae6e42d058ccbf6d814df6.jpg\",\n            \"http://imgsrc.baidu.com/forum/w%3D580/sign=5f8e25ea6159252da3171d0c049a032c/c31e4134970a304ea368670ad0c8a786c8175cfd.jpg\",\n            \"http://imgsrc.baidu.com/forum/w%3D580/sign=1cdfbb940823dd542173a760e10bb3df/7491f603738da977eede41dbb151f8198718e34b.jpg\",\n            \"http://imgsrc.baidu.com/forum/w%3D580/sign=4dab1be4b999a9013b355b3e2d940a58/45ed54e736d12f2eeba369904ec2d56284356899.jpg\",\n            \"http://imgsrc.baidu.com/forum/w%3D580/sign=0e493f7f6d81800a6ee58906813433d6/087bdab44aed2e7354c540dc8601a18b87d6fa10.jpg\",\n            \"http://imgsrc.baidu.com/forum/w%3D580/sign=5d232af3c995d143da76e42b43f18296/6f0ed9f9d72a60593aa927ea2934349b023bba82.jpg\",\n            \"http://imgsrc.baidu.com/forum/w%3D580/sign=0ddb882c55e736d158138c00ab524ffc/d8cc7b899e510fb3baf1b167d833c895d0430c53.jpg\",\n            \"http://imgsrc.baidu.com/forum/w%3D580/sign=9f40b92d2f738bd4c421b2399189876c/f5ee76094b36acaf6b29500c7dd98d1000e99c72.jpg\",\n            \"http://imgsrc.baidu.com/forum/w%3D580/sign=8afdd2ffb90e7bec23da03e91f2cb9fa/ea0635fae6cd7b892dadfc730e2442a7d8330e7a.jpg\",\n            \"http://imgsrc.baidu.com/forum/w%3D580/sign=93d365e396dda144da096cba82b6d009/e889d43f8794a4c2e21a26db0ff41bd5ad6e3902.jpg\",\n            \"http://imgsrc.baidu.com/forum/w%3D580/sign=fbd248feb03533faf5b6932698d2fdca/b5d5b31c8701a18b65dcd3bf9f2f07082838fe09.jpg\",\n            \"http://imgsrc.baidu.com/forum/w%3D580/sign=9a53b22d2f738bd4c421b2399189876c/f5ee76094b36acaf6e3a5b0c7dd98d1000e99c6d.jpg\",\n            \"http://imgsrc.baidu.com/forum/w%3D580/sign=860db23bfaedab6474724dc8c737af81/65b4c9ea15ce36d3f73b4fc03bf33a87e950b100.jpg\",\n            \"http://imgsrc.baidu.com/forum/w%3D580/sign=5355f395b219ebc4c0787691b227cf79/d751352ac65c103879270232b3119313b17e89e2.jpg\",\n            \"http://imgsrc.baidu.com/forum/w%3D580/sign=5b9a20f3c995d143da76e42b43f18296/6f0ed9f9d72a60593c102dea2934349b033bba3b.jpg\",\n            \"http://imgsrc.baidu.com/forum/w%3D580/sign=a2c8a36fa50f4bfb8cd09e5c334d788f/0a9a033b5bb5c9ea3504cf13d439b6003bf3b348.jpg\",\n            \"http://imgsrc.baidu.com/forum/w%3D580/sign=5cdae018bd3eb13544c7b7b3961ca8cb/10728bd4b31c87017d5b7e16267f9e2f0608ff57.jpg\",\n            \"http://imgsrc.baidu.com/forum/w%3D580/sign=f26782499f510fb37819779fe932c893/55610c338744ebf80ee6a8cfd8f9d72a6159a7a8.jpg\",\n            \"http://imgsrc.baidu.com/forum/w%3D580/sign=962968e396dda144da096cba82b5d009/e889d43f8794a4c2e7e02bdb0ff41bd5ac6e3904.jpg\",\n            \"http://imgsrc.baidu.com/forum/w%3D580/sign=9fecc1079e3df8dca63d8f99fd1072bf/34d062d9f2d3572cfefc532c8b13632762d0c322.jpg\",\n            \"http://imgsrc.baidu.com/forum/w%3D580/sign=0a5c61904ec2d562f208d0e5d71090f3/7ca6d933c895d1431e2f2bd372f082025baf07e2.jpg\",\n            \"http://imgsrc.baidu.com/forum/w%3D580/sign=15d49641810a19d8cb03840d03fb82c9/e4b54aed2e738bd46a777dc1a08b87d6267ff9ea.jpg\",\n            \"http://imgsrc.baidu.com/forum/w%3D580/sign=24b997e24e4a20a4311e3ccfa0539847/0aa95edf8db1cb132efbf3e3dc54564e93584bb5.jpg\",\n            \"http://imgsrc.baidu.com/forum/w%3D580/sign=f20df717902397ddd679980c6983b216/ac44d688d43f879433f786ded31b0ef41bd53a33.jpg\",\n            \"http://imgsrc.baidu.com/forum/w%3D580/sign=65b180e7cefc1e17fdbf8c397a91f67c/c5f3b2119313b07eec9867f90dd7912397dd8c1e.jpg\",\n            \"http://imgsrc.baidu.com/forum/w%3D580/sign=d80f61e663d0f703e6b295d438fb5148/c3fbaf51f3deb48fde48962ff11f3a292df5781a.jpg\",\n            \"http://imgsrc.baidu.com/forum/w%3D580/sign=b1e6a10d7dd98d1076d40c39113eb807/6c67d0160924ab180810d6c634fae6cd7b890b39.jpg\",\n            \"http://imgsrc.baidu.com/forum/w%3D580/sign=044ea385ac345982c58ae59a3cf5310b/b995a4c27d1ed21b9d671914ac6eddc450da3f91.jpg\",\n            \"http://imgsrc.baidu.com/forum/w%3D580/sign=d9d7f933b3119313c743ffb855390c10/7911b912c8fcc3ce4969fec29345d688d53f2092.jpg\",\n            \"http://imgsrc.baidu.com/forum/w%3D580/sign=1c4f6fe34e4a20a4311e3ccfa0539847/0aa95edf8db1cb13160d0be2dc54564e93584bdf.jpg\",\n            \"http://imgsrc.baidu.com/forum/w%3D580/sign=420da6ce4afbfbeddc59367748f1f78e/3d3a5bb5c9ea15ce2e71d7f8b7003af33b87b2a0.jpg\",\n            \"http://imgsrc.baidu.com/forum/w%3D580/sign=1c64b11f9d82d158bb8259b9b00b19d5/8e50f8198618367a17d9482c2f738bd4b21ce5f4.jpg\",\n            \"http://imgsrc.baidu.com/forum/w%3D580/sign=56cfcfd0738b4710ce2ffdc4f3cfc3b2/97ed8a13632762d01b6d934ea1ec08fa503dc6f5.jpg\",\n            \"http://imgsrc.baidu.com/forum/w%3D580/sign=451567f90dd79123e0e0947c9d355917/c2029245d688d43f0801a6037c1ed21b0ff43b93.jpg\",\n            \"http://imgsrc.baidu.com/forum/w%3D580/sign=9f8059fd0dd79123e0e0947c9d355917/c2029245d688d43fd29498077c1ed21b0ef43b02.jpg\",\n            \"http://imgsrc.baidu.com/forum/w%3D580/sign=a87aa5940eb30f24359aec0bf897d192/32328744ebf81a4c82fce23cd62a6059242da67e.jpg\",\n            \"http://imgsrc.baidu.com/forum/w%3D580/sign=6947bb51a5c27d1ea5263bcc2bd4adaf/036c55fbb2fb4316d05a738f21a4462308f7d3fb.jpg\",\n            \"http://imgsrc.baidu.com/forum/w%3D580/sign=88dc2cfd5243fbf2c52ca62b807fca1e/f310728b4710b912867cff0bc2fdfc0393452282.jpg\",\n            \"http://imgsrc.baidu.com/forum/w%3D580/sign=b4ec4ecb0df3d7ca0cf63f7ec21dbe3c/684f9258d109b3deb04ced9dcdbf6c81810a4c50.jpg\",\n            \"http://imgsrc.baidu.com/forum/w%3D580/sign=5da230f68644ebf86d716437e9f8d736/823fb13533fa828b90dc9416fc1f4134960a5a4c.jpg\",\n            \"http://imgsrc.baidu.com/forum/w%3D580/sign=7f214f4e4610b912bfc1f6f6f3fcfcb5/b412632762d0f70323bf9d2909fa513d2697c533.jpg\",\n            \"http://imgsrc.baidu.com/forum/w%3D580/sign=5a50e9c234fae6cd0cb4ab693fb20f9e/80086b63f6246b60c148ba81eaf81a4c500fa286.jpg\",\n            \"http://imgsrc.baidu.com/forum/w%3D580/sign=4e38f2fd9a504fc2a25fb00dd5dce7f0/312542a7d933c895b055e68ed01373f082020018.jpg\",\n            \"http://imgsrc.baidu.com/forum/w%3D580/sign=7b91f8d98601a18bf0eb1247ae2e0761/92ae2edda3cc7cd97d554a2c3801213fb90e91c8.jpg\",\n            \"http://imgsrc.baidu.com/forum/w%3D580/sign=8c8671363b87e9504217f3642039531b/05c69f3df8dcd100802786d4738b4710b8122f88.jpg\",\n            \"http://imgsrc.baidu.com/forum/w%3D580/sign=5a0944760e2442a7ae0efdade142ad95/b945ebf81a4c510f1da19cef6159252dd52aa5db.jpg\",\n            \"http://imgsrc.baidu.com/forum/w%3D580/sign=5206ff1b9d82d158bb8259b9b00b19d5/8e50f8198618367a59bb06282f738bd4b31ce512.jpg\",\n            \"http://imgsrc.baidu.com/forum/w%3D580/sign=02a1b147b3b7d0a27bc90495fbee760d/431fd21b0ef41bd502fb350150da81cb38db3d98.jpg\",\n            \"http://imgsrc.baidu.com/forum/w%3D580/sign=34b39035f636afc30e0c3f6d831beb85/eb38b6003af33a87deacddefc75c10385243b507.jpg\",\n            \"http://imgsrc.baidu.com/forum/w%3D580/sign=6bb54dca4afbfbeddc59367748f1f78e/3d3a5bb5c9ea15ce07c93cfcb7003af33a87b225.jpg\",\n            \"http://imgsrc.baidu.com/forum/w%3D580/sign=59aa60ab54fbb2fb342b581a7f4b2043/deff9925bc315c6034ec03198cb1cb13485477cf.jpg\",\n            \"http://imgsrc.baidu.com/forum/w%3D580/sign=18d5d6c87aec54e741ec1a16893a9bfd/0bfbe6cd7b899e51365961e243a7d933c9950d75.jpg\",\n            \"http://imgsrc.baidu.com/forum/w%3D580/sign=082508cfc83d70cf4cfaaa05c8ddd1ba/347a02087bf40ad115c29d15562c11dfa9ecce27.jpg\",\n            \"http://imgsrc.baidu.com/forum/w%3D580/sign=4cff68ce29381f309e198da199034c67/0700213fb80e7bec863759172e2eb9389a506b5c.jpg\",\n            \"http://imgsrc.baidu.com/forum/w%3D580/sign=05fb8402b64543a9f51bfac42e158a7b/f85d10385343fbf204f3fcd5b17eca8064388f6e.jpg\",\n            \"http://imgsrc.baidu.com/forum/w%3D580/sign=de240b320bd162d985ee621421dea950/bb34e5dde71190efaaead729cf1b9d16fdfa6030.jpg\",\n            \"http://imgsrc.baidu.com/forum/w%3D580/sign=fa597c3230adcbef01347e0e9cad2e0e/25d4ad6eddc451daab30491db7fd5266d1163206.jpg\",\n            \"http://imgsrc.baidu.com/forum/w%3D580/sign=be0e4bef2cf5e0feee1889096c6134e5/3454b319ebc4b7450db8f3e0cefc1e178a82151c.jpg\",\n            \"http://imgsrc.baidu.com/forum/w%3D580/sign=0899a0d572f082022d9291377bf9fb8a/1b2cd42a2834349ba6e92a73c8ea15ce37d3be5e.jpg\",\n            \"http://imgsrc.baidu.com/forum/w%3D580/sign=85297e35d6ca7bcb7d7bc7278e086b3f/ac59d109b3de9c82267fbe796d81800a19d8432b.jpg\",\n            \"http://imgsrc.baidu.com/forum/w%3D580/sign=02443856f703738dde4a0c2a831ab073/5b390cd7912397dd1a01dff25882b2b7d1a287c9.jpg\",\n            \"http://imgsrc.baidu.com/forum/w%3D580/sign=514075ce29381f309e198da199004c67/0700213fb80e7bec9b8844172e2eb9389a506bf3.jpg\",\n            \"http://imgsrc.baidu.com/forum/w%3D580/sign=03f27d750e2442a7ae0efdade142ad95/b945ebf81a4c510f445aa5ec6159252dd52aa5af.jpg\",\n            \"http://imgsrc.baidu.com/forum/w%3D580/sign=7f75c071d1a20cf44690fed7460b4b0c/ec1a0ef41bd5ad6efb33231c80cb39dbb7fd3c7a.jpg\",\n            \"http://imgsrc.baidu.com/forum/w%3D580/sign=af7a0b36f636afc30e0c3f6d8318eb85/eb38b6003af33a87456546ecc75c10385343b539.jpg\",\n            \"http://imgsrc.baidu.com/forum/w%3D580/sign=516aae92574e9258a63486e6ac83d1d1/4d8ca9773912b31be8188bdf8718367adbb4e1d5.jpg\",\n            \"http://imgsrc.baidu.com/forum/w%3D580/sign=634145172e2eb938ec6d7afae56385fe/a0500fb30f2442a7ee95bb53d043ad4bd01302a9.jpg\",\n            \"http://imgsrc.baidu.com/forum/w%3D580/sign=77d25e33e1fe9925cb0c695804aa5ee4/8d18ebc4b74543a9cfddbc3a1f178a82b8011406.jpg\",\n            \"http://imgsrc.baidu.com/forum/w%3D580/sign=b0c5e41ba044ad342ebf878fe0a00c08/ea3e8794a4c27d1ef3fd7d321ad5ad6edcc43869.jpg\",\n            \"http://imgsrc.baidu.com/forum/w%3D580/sign=873c7835d6ca7bcb7d7bc7278e086b3f/ac59d109b3de9c82246ab8796d81800a19d8433e.jpg\",\n            \"http://imgsrc.baidu.com/forum/w%3D580/sign=d66a7e93b219ebc4c0787691b227cf79/d751352ac65c1038fc188f34b3119313b17e89e7.jpg\",\n            \"http://imgsrc.baidu.com/forum/w%3D580/sign=b645525158ee3d6d22c687c373176d41/04282df5e0fe99255b4928a635a85edf8cb17184.jpg\",\n            \"http://imgsrc.baidu.com/forum/w%3D580/sign=291b9d96342ac65c6705667bcbf3b21d/c6ddd100baa1cd11611733d6b812c8fcc2ce2da7.jpg\",\n            \"http://imgsrc.baidu.com/forum/w%3D580/sign=44498e34b3119313c743ffb855390c10/7911b912c8fcc3ced4f789c59345d688d43f2015.jpg\",\n            \"http://imgsrc.baidu.com/forum/w%3D580/sign=edd124fb279759ee4a5060c382fa434e/ce1e3a292df5e0fe7c0ec9f75d6034a85edf7237.jpg\",\n            \"http://imgsrc.baidu.com/forum/w%3D580/sign=8fa6a4c6bba1cd1105b672288913c8b0/692d11dfa9ec8a13145d1716f603918fa1ecc086.jpg\",\n            \"http://imgsrc.baidu.com/forum/w%3D580/sign=597936d06c224f4a5799731b39f69044/626134a85edf8db120913c920823dd54574e748e.jpg\",\n            \"http://imgsrc.baidu.com/forum/w%3D580/sign=aa410e36f636afc30e0c3f6d8318eb85/eb38b6003af33a87405e43ecc75c10385343b512.jpg\",\n            \"http://imgsrc.baidu.com/forum/w%3D580/sign=bdbbb53bfd039245a1b5e107b795a4a8/9eed08fa513d2697279b4bab54fbb2fb4216d8f9.jpg\",\n            \"http://imgsrc.baidu.com/forum/w%3D580/sign=78813ace6f061d957d4637304bf50a5d/112fb9389b504fc204e2f3f3e4dde71191ef6d8c.jpg\",\n            \"http://imgsrc.baidu.com/forum/w%3D580/sign=198dcffe9a504fc2a25fb00dd5dce7f0/312542a7d933c895e7e0db8dd01373f0830200ae.jpg\",\n            \"http://imgsrc.baidu.com/forum/w%3D580/sign=f959e6949922720e7bcee2f24bca0a3a/3722dd54564e925821a7c5189d82d158cdbf4eb2.jpg\",\n            \"http://imgsrc.baidu.com/forum/w%3D580/sign=2475aa0250da81cb4ee683c56267d0a4/782209f790529822fb157b35d6ca7bcb0a46d427.jpg\",\n            \"http://imgsrc.baidu.com/forum/w%3D580/sign=0756346f8326cffc692abfba89004a7d/6d42fbf2b2119313006dbd4664380cd791238d1f.jpg\",\n            \"http://imgsrc.baidu.com/forum/w%3D580/sign=d830a0ec6159252da3171d0c0499032c/c31e4134970a304e24d6e20cd0c8a786c8175c54.jpg\",\n            \"http://imgsrc.baidu.com/forum/w%3D580/sign=d30b87d93b292df597c3ac1d8c335ce2/47300a55b319ebc4f0a0346f8326cffc1f171668.jpg\",\n            \"http://imgsrc.baidu.com/forum/w%3D580/sign=05db57e8b8389b5038ffe05ab534e5f1/31b20f2442a7d933fb9fa985ac4bd11373f00115.jpg\",\n            \"http://imgsrc.baidu.com/forum/w%3D580/sign=04370ef70b55b3199cf9827d73ab8286/0486e950352ac65cbbcc2485faf2b21192138a78.jpg\",\n            \"http://imgsrc.baidu.com/forum/w%3D580/sign=b286ca3d42166d223877159c76220945/82305c6034a85edf6accb3d548540923dc547581.jpg\",\n            \"http://imgsrc.baidu.com/forum/w%3D580/sign=dc231a1ff3d3572c66e29cd4ba116352/d91090ef76c6a7ef18e9e5d0fcfaaf51f2de667e.jpg\",\n            \"http://imgsrc.baidu.com/forum/w%3D580/sign=18bb3b6f8326cffc692abfba89034a7d/6d42fbf2b21193131f80b24664380cd790238d02.jpg\",\n            \"http://imgsrc.baidu.com/forum/w%3D580/sign=d2da1cfe0dd79123e0e0947c9d355917/c2029245d688d43f9fcedd047c1ed21b0ff43be5.jpg\",\n            \"http://imgsrc.baidu.com/forum/w%3D580/sign=fc89a80ea686c91708035231f93c70c6/97004c086e061d9563387bce7af40ad163d9cacf.jpg\",\n            \"http://imgsrc.baidu.com/forum/w%3D580/sign=b292ce498d5494ee87220f111df4e0e1/46f1f736afc379314b19c9dfeac4b74542a911d7.jpg\",\n            \"http://imgsrc.baidu.com/forum/w%3D580/sign=361efacc18d8bc3ec60806c2b28aa6c8/74ec2e738bd4b31cdd9edb4d86d6277f9f2ff8a3.jpg\",\n            \"http://imgsrc.baidu.com/forum/w%3D580/sign=1e1d43d4b21bb0518f24b320067bda77/9f45ad345982b2b767c8763230adcbef77099bf6.jpg\",\n            \"http://imgsrc.baidu.com/forum/w%3D580/sign=13f61dcfc83d70cf4cfaaa05c8ded1ba/347a02087bf40ad10e118815562c11dfa8ecce54.jpg\",\n            \"http://imgsrc.baidu.com/forum/w%3D580/sign=24b4aeec2934349b74066e8df9eb1521/0e4f251f95cad1c8a61fb3d17e3e6709c83d51a4.jpg\",\n            \"http://imgsrc.baidu.com/forum/w%3D580/sign=257807d8d31b0ef46ce89856edc551a1/8cfa43166d224f4aa11e3ce508f790529922d146.jpg\",\n            \"http://imgsrc.baidu.com/forum/w%3D580/sign=19389002b64543a9f51bfac42e168a7b/f85d10385343fbf21830e8d5b17eca8065388f2d.jpg\",\n            \"http://imgsrc.baidu.com/forum/w%3D580/sign=8649b7796d81800a6ee58906813433d6/087bdab44aed2e73dcc5c8da8601a18b87d6fa1d.jpg\",\n            \"http://imgsrc.baidu.com/forum/w%3D580/sign=1e9d40019e3df8dca63d8f99fd1072bf/34d062d9f2d3572c7f8dd22a8b13632763d0c3de.jpg\",\n            \"http://imgsrc.baidu.com/forum/w%3D580/sign=427efdde377adab43dd01b4bbbd5b36b/eea30cf431adcbefcc6e30f0adaf2edda2cc9feb.jpg\",\n            \"http://imgsrc.baidu.com/forum/w%3D580/sign=cef2b2d17e3e6709be0045f70bc69fb8/50071d950a7b02081280711763d9f2d3562cc8f8.jpg\",\n            \"http://imgsrc.baidu.com/forum/w%3D580/sign=dad8621ebd3eb13544c7b7b3961ca8cb/10728bd4b31c8701fb59fc10267f9e2f0608ff5e.jpg\",\n            \"http://imgsrc.baidu.com/forum/w%3D580/sign=dc6b1efe0dd79123e0e0947c9d365917/c2029245d688d43f917fdf047c1ed21b0ff43b76.jpg\",\n            \"http://imgsrc.baidu.com/forum/w%3D580/sign=3c134abcdbb44aed594ebeec831d876a/32f531adcbef7609f13dd0692fdda3cc7cd99e17.jpg\",\n            \"http://imgsrc.baidu.com/forum/w%3D580/sign=59d9a692574e9258a63486e6ac80d1d1/4d8ca9773912b31be0ab83df8718367adbb4e106.jpg\",\n            \"http://imgsrc.baidu.com/forum/w%3D580/sign=cefb703230adcbef01347e0e9cae2e0e/25d4ad6eddc451da9f92451db7fd5266d11632e4.jpg\",\n            \"http://imgsrc.baidu.com/forum/w%3D580/sign=5b717bce29381f309e198da199004c67/0700213fb80e7bec91b94a172e2eb9389a506be2.jpg\",\n            \"http://imgsrc.baidu.com/forum/w%3D580/sign=d089ec2977c6a7efb926a82ecdfbafe9/4df182025aafa40f7c63872baa64034f79f0199b.jpg\",\n            \"http://imgsrc.baidu.com/forum/w%3D580/sign=b87c8bc5908fa0ec7fc764051696594a/cddfb48f8c5494ee2ab346ef2cf5e0fe98257eb0.jpg\",\n            \"http://imgsrc.baidu.com/forum/w%3D580/sign=4327c0f8b03533faf5b6932698d1fdca/b5d5b31c8701a18bdd295bb99f2f07082938fe03.jpg\",\n            \"http://imgsrc.baidu.com/forum/w%3D580/sign=df5419fe0dd79123e0e0947c9d365917/c2029245d688d43f9240d8047c1ed21b0ff43b5f.jpg\",\n            \"http://imgsrc.baidu.com/forum/w%3D580/sign=f479e0d0fcfaaf5184e381b7bc5594ed/75fafbedab64034f42928af0aec379310b551ded.jpg\",\n            \"http://imgsrc.baidu.com/forum/w%3D580/sign=ee7562f6e824b899de3c79305e071d59/860f7bec54e736d1e963c4fe9a504fc2d46269d1.jpg\",\n            \"http://imgsrc.baidu.com/forum/w%3D580/sign=e1a1aa0c7acb0a4685228b315b62f63e/ef08b3de9c82d15821ed1147810a19d8bd3e42de.jpg\",\n            \"http://imgsrc.baidu.com/forum/w%3D580/sign=599c2c1c80cb39dbc1c0675ee01709a7/37f690529822720e88abaa0c7acb0a46f31fabe4.jpg\",\n            \"http://imgsrc.baidu.com/forum/w%3D580/sign=bec447353b87e9504217f364203a531b/05c69f3df8dcd100b265b0d7738b4710b8122f4f.jpg\",\n            \"http://imgsrc.baidu.com/forum/w%3D580/sign=fad2fa82eaf81a4c2632ecc1e7286029/8f3433fa828ba61e5e3280d94034970a314e596d.jpg\",\n            \"http://imgsrc.baidu.com/forum/w%3D580/sign=4009c1f8b03533faf5b6932698d1fdca/b5d5b31c8701a18bde075ab99f2f07082938fe5d.jpg\",\n            \"http://imgsrc.baidu.com/forum/w%3D580/sign=e687ccf332fa828bd1239debcd1e41cd/8d1d8701a18b87d67dc6e3e9060828381e30fd45.jpg\",\n            \"http://imgsrc.baidu.com/forum/w%3D580/sign=6a17d2692fdda3cc0be4b82831e83905/07dab6fd5266d0162805d5de962bd40735fa352c.jpg\",\n            \"http://imgsrc.baidu.com/forum/w%3D580/sign=dca27493b219ebc4c0787691b227cf79/d751352ac65c1038f6d08534b3119313b17e899f.jpg\",\n            \"http://imgsrc.baidu.com/forum/w%3D580/sign=31fe32da8601a18bf0eb1247ae2e0761/92ae2edda3cc7cd9373a802f3801213fb80e9136.jpg\",\n            \"http://imgsrc.baidu.com/forum/w%3D580/sign=1a56139ed009b3deebbfe460fcbe6cd3/0713b31bb051f819ca00b7bcdbb44aed2e73e724.jpg\",\n            \"http://imgsrc.baidu.com/forum/w%3D580/sign=459b140cd0c8a786be2a4a065708c9c7/8698a9014c086e067d118bbd03087bf40bd1cb88.jpg\",\n            \"http://imgsrc.baidu.com/forum/w%3D580/sign=57892e0578310a55c424defc87444387/04f23a87e950352a66e391fe5243fbf2b3118b43.jpg\",\n            \"http://imgsrc.baidu.com/forum/w%3D580/sign=fd451e44d058ccbf1bbcb53229d9bcd4/c6188618367adab40476acb58ad4b31c8601e4a7.jpg\",\n            \"http://imgsrc.baidu.com/forum/w%3D580/sign=97fdd4889358d109c4e3a9bae159ccd0/97763912b31bb051b2b004de377adab44bede0b5.jpg\",\n            \"http://imgsrc.baidu.com/forum/w%3D580/sign=9d1030189d82d158bb8259b9b00b19d5/8e50f8198618367a96adc92b2f738bd4b31ce525.jpg\",\n            \"http://imgsrc.baidu.com/forum/w%3D580/sign=ad7a753cae51f3dec3b2b96ca4eff0ec/c5ecab64034f78f0c75a270578310a55b2191cf7.jpg\",\n            \"http://imgsrc.baidu.com/forum/w%3D580/sign=27b48fce29381f309e198da199004c67/0700213fb80e7beced7cbe172e2eb9389a506ba7.jpg\",\n            \"http://imgsrc.baidu.com/forum/w%3D580/sign=622e0a4da71ea8d38a22740ca70830cf/9f8a87d6277f9e2f56dca0fe1e30e924b999f358.jpg\",\n            \"http://imgsrc.baidu.com/forum/w%3D580/sign=5d2697172e2eb938ec6d7afae56385fe/a0500fb30f2442a7d0f26953d043ad4bd013024c.jpg\",\n            \"http://imgsrc.baidu.com/forum/w%3D580/sign=8cc62652a5c27d1ea5263bcc2bd7adaf/036c55fbb2fb431635dbee8c21a4462308f7d305.jpg\",\n            \"http://imgsrc.baidu.com/forum/w%3D580/sign=551798353b87e9504217f3642039531b/05c69f3df8dcd10059b66fd7738b4710b9122f1b.jpg\",\n            \"http://imgsrc.baidu.com/forum/w%3D580/sign=5fbf440b7a899e51788e3a1c72a6d990/c8256b600c33874481bdb88a500fd9f9d62aa0c9.jpg\",\n            \"http://imgsrc.baidu.com/forum/w%3D580/sign=fdaadfd8d31b0ef46ce89856edc551a1/8cfa43166d224f4a79cce4e508f790529922d1f4.jpg\",\n            \"http://imgsrc.baidu.com/forum/w%3D580/sign=46110fa8dcc451daf6f60ce386ff52a5/1ea5462309f79052c5e6d0c80df3d7ca7acbd548.jpg\",\n            \"http://imgsrc.baidu.com/forum/w%3D580/sign=76761ab8cb8065387beaa41ba7dca115/fdcfc3fdfc03924533ed7ef98694a4c27c1e25e9.jpg\",\n            \"http://imgsrc.baidu.com/forum/w%3D580/sign=d6cbfbc9d8f9d72a17641015e42b282a/981fa8d3fd1f41345b8d9a88241f95cad0c85e8b.jpg\",\n            \"http://imgsrc.baidu.com/forum/w%3D580/sign=ea716c9a113853438ccf8729a312b01f/84a0cd11728b47106649623ac2cec3fdfd0323e7.jpg\",\n            \"http://imgsrc.baidu.com/forum/w%3D580/sign=3ac7e056f703738dde4a0c2a8319b073/5b390cd7912397dd228207f25882b2b7d1a2874a.jpg\",\n            \"http://imgsrc.baidu.com/forum/w%3D580/sign=d930090a7dd98d1076d40c39113db807/6c67d0160924ab1860c67ec134fae6cd7a890b71.jpg\",\n            \"http://imgsrc.baidu.com/forum/w%3D580/sign=f7e4da093ac79f3d8fe1e4388aa3cdbc/45f50ad162d9f2d369f5bb19a8ec8a136227cc65.jpg\",\n            \"http://imgsrc.baidu.com/forum/w%3D580/sign=2f2392d4b21bb0518f24b320067bda77/9f45ad345982b2b756f6a73230adcbef77099bd8.jpg\",\n            \"http://imgsrc.baidu.com/forum/w%3D580/sign=816317f75d6034a829e2b889fb1249d9/7da88226cffc1e1792c412c74b90f603728de9ca.jpg\",\n            \"http://imgsrc.baidu.com/forum/w%3D580/sign=f60102b13812b31bc76ccd21b6193674/a9dca144ad345982373b7b640df431adcaef8490.jpg\",\n            \"http://imgsrc.baidu.com/forum/w%3D580/sign=14517cec6159252da3171d0c049a032c/c31e4134970a304ee8b73e0cd0c8a786c8175cb4.jpg\",\n            \"http://imgsrc.baidu.com/forum/w%3D580/sign=47319b1ba2cc7cd9fa2d34d109002104/88fc5266d0160924408622edd50735fae7cd34a2.jpg\",\n            \"http://imgsrc.baidu.com/forum/w%3D580/sign=935fd71ff3d3572c66e29cd4ba126352/d91090ef76c6a7ef579528d0fcfaaf51f2de6692.jpg\",\n            \"http://imgsrc.baidu.com/forum/w%3D580/sign=b4b26f724bed2e73fce98624b703a16d/0faccbef76094b362e679b1ba2cc7cd98c109d54.jpg\",\n            \"http://imgsrc.baidu.com/forum/w%3D580/sign=55d22449a1ec08fa260013af69ec3d4d/8a8e8c5494eef01f98f89833e1fe9925bd317d5d.jpg\",\n            \"http://imgsrc.baidu.com/forum/w%3D580/sign=a2381a692fdda3cc0be4b82831e83905/07dab6fd5266d016e02a1dde962bd40735fa3512.jpg\",\n            \"http://imgsrc.baidu.com/forum/w%3D580/sign=4c96816e5fdf8db1bc2e7c6c3921dddb/f1fd1e178a82b9015a6643c5728da9773812ef7a.jpg\",\n            \"http://imgsrc.baidu.com/forum/w%3D580/sign=e61f709a113853438ccf8729a312b01f/84a0cd11728b47106a277e3ac2cec3fdfc032339.jpg\",\n            \"http://imgsrc.baidu.com/forum/w%3D580/sign=fe761997f2deb48ffb69a1d6c01e3aef/9565034f78f0f736ac4fcff70b55b319eac41397.jpg\",\n            \"http://imgsrc.baidu.com/forum/w%3D580/sign=41d01bf25882b2b7a79f39cc01accb0a/9ac37d1ed21b0ef4fb411ba8dcc451da80cb3e98.jpg\",\n            \"http://imgsrc.baidu.com/forum/w%3D580/sign=ec47bc1763d9f2d3201124e799ed8a53/dbdce71190ef76c6305c78dd9c16fdfaae5167bf.jpg\",\n            \"http://imgsrc.baidu.com/forum/w%3D580/sign=a05880172e2eb938ec6d7afae56385fe/a0500fb30f2442a72d8c7e53d043ad4bd0130243.jpg\",\n            \"http://imgsrc.baidu.com/forum/w%3D580/sign=f90c20edd50735fa91f04eb1ae500f9f/cc1ebe096b63f6243974bbf58644ebf81a4ca318.jpg\",\n            \"http://imgsrc.baidu.com/forum/w%3D580/sign=8de44ad94034970a47731027a5cbd1c0/a02e070828381f30cc130f5fa8014c086e06f03b.jpg\",\n            \"http://imgsrc.baidu.com/forum/w%3D580/sign=8dc07ad7738b4710ce2ffdc4f3cfc3b2/97ed8a13632762d0c0622649a1ec08fa503dc6ed.jpg\",\n            \"http://imgsrc.baidu.com/forum/w%3D580/sign=4be2f0d6b812c8fcb4f3f6c5cc0292b4/5d2662d0f703918f9985bf3c503d269758eec4f5.jpg\",\n            \"http://imgsrc.baidu.com/forum/w%3D580/sign=cd498e1db7fd5266a72b3c1c9b199799/a623720e0cf3d7ca3749bd80f31fbe096a63a98e.jpg\",\n            \"http://imgsrc.baidu.com/forum/w%3D580/sign=972a63f98694a4c20a23e7233ef51bac/67ef3d6d55fbb2fb02b4dae44e4a20a44723dcae.jpg\",\n            \"http://imgsrc.baidu.com/forum/w%3D580/sign=973de61c80cb39dbc1c0675ee01409a7/37f690529822720e460a600c7acb0a46f31fab05.jpg\",\n            \"http://imgsrc.baidu.com/forum/w%3D580/sign=4023f9d6b812c8fcb4f3f6c5cc0292b4/5d2662d0f703918f9244b63c503d269758eec4b4.jpg\",\n            \"http://imgsrc.baidu.com/forum/w%3D580/sign=d53db480f31fbe091c5ec31c5b620c30/a283d158ccbf6c812efca71ebd3eb13532fa407a.jpg\",\n            \"http://imgsrc.baidu.com/forum/w%3D580/sign=df4afc6f8326cffc692abfba89004a7d/6d42fbf2b2119313d871754664380cd791238d13.jpg\",\n            \"http://imgsrc.baidu.com/forum/w%3D580/sign=05146292574e9258a63486e6ac83d1d1/4d8ca9773912b31bbc6647df8718367adab4e13b.jpg\",\n            \"http://imgsrc.baidu.com/forum/w%3D580/sign=f571dd16f603918fd7d13dc2613c264b/9150f3deb48f8c5402b84fd93b292df5e1fe7fda.jpg\",\n            \"http://imgsrc.baidu.com/forum/w%3D580/sign=0e1403f8b03533faf5b6932698d1fdca/b5d5b31c8701a18b901a98b99f2f07082938fe50.jpg\",\n            \"http://imgsrc.baidu.com/forum/w%3D580/sign=463d5217f603918fd7d13dc2613c264b/9150f3deb48f8c54b1f4c0d83b292df5e0fe7f26.jpg\",\n            \"http://imgsrc.baidu.com/forum/w%3D580/sign=4657db03b64543a9f51bfac42e168a7b/f85d10385343fbf2475fa3d4b17eca8064388fd3.jpg\",\n            \"http://imgsrc.baidu.com/forum/w%3D580/sign=e2040b343b87e9504217f3642039531b/05c69f3df8dcd100eea5fcd6738b4710b9122f08.jpg\",\n            \"http://imgsrc.baidu.com/forum/w%3D580/sign=d2ef4d1e8c1001e94e3c1407880f7b06/ee170924ab18972ba524043de7cd7b899e510a2f.jpg\",\n            \"http://imgsrc.baidu.com/forum/w%3D580/sign=514870f3960a304e5222a0f2e1c9a7c3/390928381f30e92414ce98c64d086e061c95f7e4.jpg\",\n            \"http://imgsrc.baidu.com/forum/w%3D580/sign=da21e1650df431adbcd243317b37ac0f/30f51bd5ad6eddc4394cb00c38dbb6fd5366339a.jpg\",\n            \"http://imgsrc.baidu.com/forum/w%3D580/sign=4c4a1ee9b8389b5038ffe05ab534e5f1/31b20f2442a7d933b20ee084ac4bd11372f001a7.jpg\",\n            \"http://imgsrc.baidu.com/forum/w%3D580/sign=39a3a382d788d43ff0a991fa4d1fd2aa/6f3c269759ee3d6d905b833c42166d224e4adead.jpg\",\n            \"http://imgsrc.baidu.com/forum/w%3D580/sign=25c8c91bd000baa1ba2c47b37712b9b1/ccd2572c11dfa9ec30e652e063d0f703908fc17a.jpg\",\n            \"http://imgsrc.baidu.com/forum/w%3D580/sign=0d37073de7cd7b89e96c3a8b3f254291/5562f6246b600c335e74d93f1b4c510fd9f9a13d.jpg\",\n            \"http://imgsrc.baidu.com/forum/w%3D580/sign=72d30bc6203fb80e0cd161df06d02ffb/a92ad40735fae6cd08b3ac960eb30f2443a70fc6.jpg\",\n            \"http://imgsrc.baidu.com/forum/w%3D580/sign=3456413783025aafd3327ec3cbecab8d/ea2b2834349b033bbef0fb2d14ce36d3d439bdca.jpg\",\n            \"http://imgsrc.baidu.com/forum/w%3D580/sign=705e404c4610b912bfc1f6f6f3fcfcb5/b412632762d0f7032cc0922b09fa513d2797c5dc.jpg\",\n            \"http://imgsrc.baidu.com/forum/w%3D580/sign=5dee7d8d21a446237ecaa56aa8207246/60de8db1cb13495439efef93574e9258d0094a50.jpg\",\n            \"http://imgsrc.baidu.com/forum/w%3D580/sign=f4e69428cf1b9d168ac79a69c3dfb4eb/64aea40f4bfbfbed2944308879f0f736afc31f2c.jpg\",\n            \"http://imgsrc.baidu.com/forum/w%3D580/sign=73a20ac6203fb80e0cd161df06d02ffb/a92ad40735fae6cd09c2ad960eb30f2443a70fd7.jpg\",\n            \"http://imgsrc.baidu.com/forum/w%3D580/sign=6ca54ed9d31b0ef46ce89856edc551a1/8cfa43166d224f4ae8c375e408f790529922d1f4.jpg\",\n            \"http://imgsrc.baidu.com/forum/w%3D580/sign=0ca9bed700e9390156028d364bed54f9/3725ab18972bd407458ad50a7a899e510eb309fc.jpg\",\n            \"http://imgsrc.baidu.com/forum/w%3D580/sign=279f9f682fdda3cc0be4b82831e83905/07dab6fd5266d016658d98df962bd40734fa35b5.jpg\",\n            \"http://imgsrc.baidu.com/forum/w%3D580/sign=aa14b411267f9e2f70351d002f31e962/42d88d1001e939012aa08bc97aec54e737d196a3.jpg\",\n            \"http://imgsrc.baidu.com/forum/w%3D580/sign=fce3322e3801213fcf334ed464e536f8/9519972bd40735fa973e484e9f510fb30e24087f.jpg\",\n            \"http://imgsrc.baidu.com/forum/w%3D580/sign=413be0c034fae6cd0cb4ab693fb20f9e/80086b63f6246b60da23b383eaf81a4c510fa21d.jpg\",\n            \"http://imgsrc.baidu.com/forum/w%3D580/sign=63c349d9d31b0ef46ce89856edc551a1/8cfa43166d224f4ae7a572e408f790529822d10e.jpg\",\n            \"http://imgsrc.baidu.com/forum/w%3D580/sign=de8d7e2a2f738bd4c421b239918a876c/f5ee76094b36acaf2ae4970b7dd98d1000e99cc5.jpg\",\n            \"http://imgsrc.baidu.com/forum/w%3D580/sign=4272df03b64543a9f51bfac42e168a7b/f85d10385343fbf2437aa7d4b17eca8064388ff0.jpg\",\n            \"http://imgsrc.baidu.com/forum/w%3D580/sign=3a26e30d7acb0a4685228b315b61f63e/ef08b3de9c82d158fa6a5846810a19d8bd3e4259.jpg\",\n            \"http://imgsrc.baidu.com/forum/w%3D580/sign=f8fa8c5ea8014c08193b28ad3a79025b/6ae636d12f2eb9388a8e7b05d4628535e4dd6f62.jpg\",\n            \"http://imgsrc.baidu.com/forum/w%3D580/sign=ebdfa3cd314e251fe2f7e4f09784c9c2/16391f30e924b89903ea78cf6f061d950b7bf670.jpg\",\n            \"http://imgsrc.baidu.com/forum/w%3D580/sign=cc7b016f5fdf8db1bc2e7c6c3922dddb/f1fd1e178a82b901da8bc3c4728da9773912ef20.jpg\",\n            \"http://imgsrc.baidu.com/forum/w%3D580/sign=767df609c2fdfc03e578e3b0e43e87a9/af8ea0ec08fa513d98166c293c6d55fbb3fbd98e.jpg\",\n            \"http://imgsrc.baidu.com/forum/w%3D580/sign=f892b3df377adab43dd01b4bbbd5b36b/eea30cf431adcbef76827ef1adaf2edda2cc9f97.jpg\",\n            \"http://imgsrc.baidu.com/forum/w%3D580/sign=e58a86deeac4b7453494b71efffe1e78/0b2bc65c10385343142d87d69213b07ecb80886c.jpg\",\n            \"http://imgsrc.baidu.com/forum/w%3D580/sign=922d1ef3359b033b2c88fcd225cf3620/1b1e95cad1c8a7868f1049f96609c93d70cf500b.jpg\",\n            \"http://imgsrc.baidu.com/forum/w%3D580/sign=7cf99141c8177f3e1034fc0540ce3bb9/72096e061d950a7baf394d330bd162d9f2d3c93e.jpg\",\n            \"http://imgsrc.baidu.com/forum/w%3D580/sign=867400bddbb44aed594ebeec831d876a/32f531adcbef76094b5a9a682fdda3cc7dd99ef0.jpg\",\n            \"http://imgsrc.baidu.com/forum/w%3D580/sign=cf9551cec83d70cf4cfaaa05c8ddd1ba/347a02087bf40ad1d272c414562c11dfa8ecceb0.jpg\",\n            \"http://imgsrc.baidu.com/forum/w%3D580/sign=e9dfa1cd314e251fe2f7e4f09784c9c2/16391f30e924b89901ea7acf6f061d950b7bf670.jpg\",\n            \"http://imgsrc.baidu.com/forum/w%3D580/sign=0e17cbd84034970a47731027a5c8d1c0/a02e070828381f304fe08e5ea8014c086f06f049.jpg\",\n            \"http://imgsrc.baidu.com/forum/w%3D580/sign=8130964c86d6277fe912323018391f63/9dcd7cd98d1001e93f3016f8b90e7bec54e7973d.jpg\",\n            \"http://imgsrc.baidu.com/forum/w%3D580/sign=e0add10a7a899e51788e3a1c72a6d990/c8256b600c3387443eaf2d8b500fd9f9d62aa0e4.jpg\",\n            \"http://imgsrc.baidu.com/forum/w%3D580/sign=4dbce6c7bba1cd1105b672288913c8b0/692d11dfa9ec8a13d6475517f603918fa1ecc0ed.jpg\",\n            \"http://imgsrc.baidu.com/forum/w%3D580/sign=446a940b7dd98d1076d40c39113eb807/6c67d0160924ab18fd9ce3c034fae6cd7a890bbb.jpg\",\n            \"http://imgsrc.baidu.com/forum/w%3D580/sign=78dd0dee2cf5e0feee1889096c6134e5/3454b319ebc4b745cb6bb5e1cefc1e178b82154e.jpg\",\n            \"http://imgsrc.baidu.com/forum/w%3D580/sign=4e103d81f31fbe091c5ec31c5b620c30/a283d158ccbf6c81b5d12e1fbd3eb13532fa4067.jpg\",\n            \"http://imgsrc.baidu.com/forum/w%3D580/sign=6cf101c6203fb80e0cd161df06d02ffb/a92ad40735fae6cd1691a6960eb30f2442a70f24.jpg\",\n            \"http://imgsrc.baidu.com/forum/w%3D580/sign=441c6372c8ea15ce41eee00186013a25/9987c9177f3e67098e1a48083ac79f3df8dc552b.jpg\",\n            \"http://imgsrc.baidu.com/forum/w%3D580/sign=05898f8242a98226b8c12b2fba83b97a/2e395343fbf2b2117d5e88b9cb8065380dd78ed2.jpg\",\n            \"http://imgsrc.baidu.com/forum/w%3D580/sign=4a77011cb7fd5266a72b3c1c9b199799/a623720e0cf3d7cab0773281f31fbe096a63a941.jpg\",\n            \"http://imgsrc.baidu.com/forum/w%3D580/sign=5509f32877094b36db921be593cd7c00/e3c551da81cb39dbd2596aa1d1160924aa1830a8.jpg\",\n            \"http://imgsrc.baidu.com/forum/w%3D580/sign=0b099f28cf1b9d168ac79a69c3dcb4eb/64aea40f4bfbfbedd6ab3b8879f0f736aec31f53.jpg\",\n            \"http://imgsrc.baidu.com/forum/w%3D580/sign=6b0854e54e4a20a4311e3ccfa0539847/0aa95edf8db1cb13614a30e4dc54564e92584b22.jpg\",\n            \"http://imgsrc.baidu.com/forum/w%3D580/sign=762b03ee2cf5e0feee1889096c6134e5/3454b319ebc4b745c59dbbe1cefc1e178a821538.jpg\",\n            \"http://imgsrc.baidu.com/forum/w%3D580/sign=b3d7b3d4622762d0803ea4b790ed0849/a317fdfaaf51f3deaeb4b59395eef01f3b2979c7.jpg\",\n            \"http://imgsrc.baidu.com/forum/w%3D580/sign=0dce948cd01373f0f53f6f97940e4b8b/5e58252dd42a2834e8163afc5ab5c9ea14cebf92.jpg\",\n            \"http://imgsrc.baidu.com/forum/w%3D580/sign=dbc7f4dc9c16fdfad86cc6e6848e8cea/9a0e4bfbfbedab647f674237f636afc379311e34.jpg\",\n            \"http://imgsrc.baidu.com/forum/w%3D580/sign=24e60c162e2eb938ec6d7afae56385fe/a0500fb30f2442a7a932f252d043ad4bd113020d.jpg\",\n            \"http://imgsrc.baidu.com/forum/w%3D580/sign=70c5883c42166d223877159c76220945/82305c6034a85edfa88ff1d448540923dc5475c2.jpg\",\n            \"http://imgsrc.baidu.com/forum/w%3D580/sign=826daf0dd0c8a786be2a4a065708c9c7/8698a9014c086e06bae730bc03087bf40bd1cbff.jpg\",\n            \"http://imgsrc.baidu.com/forum/w%3D580/sign=6dbaf53b1f178a82ce3c7fa8c602737f/8c109313b07eca808aa43610902397dda04483a1.jpg\",\n            \"http://imgsrc.baidu.com/forum/w%3D580/sign=b84864188cb1cb133e693c1bed5656da/20168a82b9014a907ae3494aa8773912b21bee6d.jpg\",\n            \"http://imgsrc.baidu.com/forum/w%3D580/sign=ee0db145b8014a90813e46b599763971/8e7fca8065380cd76139ac1aa044ad3459828127.jpg\",\n            \"http://imgsrc.baidu.com/forum/w%3D580/sign=3bc50d162e2eb938ec6d7afae56385fe/a0500fb30f2442a7b611f352d043ad4bd113022e.jpg\",\n            \"http://imgsrc.baidu.com/forum/w%3D580/sign=63b044f96609c93d07f20effaf3cf8bb/23940a7b02087bf4a076591ef3d3572c10dfcfb4.jpg\",\n            \"http://imgsrc.baidu.com/forum/w%3D580/sign=3b030d162e2eb938ec6d7afae56085fe/a0500fb30f2442a7b6d7f352d043ad4bd0130268.jpg\",\n            \"http://imgsrc.baidu.com/forum/w%3D580/sign=e8bf7405d462853592e0d229a0ed76f2/e732c895d143ad4ba2fc483783025aafa50f0673.jpg\",\n            \"http://imgsrc.baidu.com/forum/w%3D580/sign=42792d18a8ec8a13141a57e8c7029157/99eece1b9d16fdfaba04cf19b58f8c5495ee7bd9.jpg\",\n            \"http://imgsrc.baidu.com/forum/w%3D580/sign=c3b657a70d3387449cc52f74610ed937/27d9bc3eb13533fab7199ad9a9d3fd1f40345b9e.jpg\",\n            \"http://imgsrc.baidu.com/forum/w%3D580/sign=6a39361663d9f2d3201124e799ee8a53/dbdce71190ef76c6b622f2dc9c16fdfaae516751.jpg\",\n            \"http://imgsrc.baidu.com/forum/w%3D580/sign=5cf37df3960a304e5222a0f2e1caa7c3/390928381f30e924197595c64d086e061c95f771.jpg\",\n            \"http://imgsrc.baidu.com/forum/w%3D580/sign=3bf3bb83eaf81a4c2632ecc1e72b6029/8f3433fa828ba61e9f13c1d84034970a314e594c.jpg\",\n            \"http://imgsrc.baidu.com/forum/w%3D580/sign=6b6a371663d9f2d3201124e799ed8a53/dbdce71190ef76c6b771f3dc9c16fdfaae5167a2.jpg\",\n            \"http://imgsrc.baidu.com/forum/w%3D580/sign=6e97343091529822053339cbe7cb7b3b/77550923dd54564ebaececceb2de9c82d0584fb8.jpg\",\n            \"http://imgsrc.baidu.com/forum/w%3D580/sign=7fab4f4c4610b912bfc1f6f6f3fcfcb5/b412632762d0f70323359d2b09fa513d2797c547.jpg\",\n            \"http://imgsrc.baidu.com/forum/w%3D580/sign=de0bf3dc9c16fdfad86cc6e6848d8cea/9a0e4bfbfbedab647aab4537f636afc378311e68.jpg\",\n            \"http://imgsrc.baidu.com/forum/w%3D580/sign=d2aece19b58f8c54e3d3c5270a2b2dee/3d4e78f0f736afc304ce3792b219ebc4b6451203.jpg\",\n    };\n\n}\n"
  },
  {
    "path": "demo/src/main/java/com/liulishuo/filedownloader/demo/DemoApplication.java",
    "content": "package com.liulishuo.filedownloader.demo;\n\nimport android.app.Application;\nimport android.content.Context;\nimport android.util.Log;\n\nimport com.liulishuo.filedownloader.FileDownloader;\nimport com.liulishuo.filedownloader.connection.FileDownloadUrlConnection;\nimport com.liulishuo.filedownloader.util.FileDownloadLog;\nimport com.liulishuo.filedownloader.util.FileDownloadUtils;\n\nimport cn.dreamtobe.threaddebugger.IThreadDebugger;\nimport cn.dreamtobe.threaddebugger.ThreadDebugger;\nimport cn.dreamtobe.threaddebugger.ThreadDebuggers;\n\n/**\n * Created by Jacksgong on 12/17/15.\n */\npublic class DemoApplication extends Application {\n    public static Context CONTEXT;\n    private final static String TAG = \"FileDownloadApplication\";\n\n    @Override\n    public void onCreate() {\n        super.onCreate();\n        // for demo.\n        CONTEXT = this;\n\n        // just for open the log in this demo project.\n        FileDownloadLog.NEED_LOG = BuildConfig.DOWNLOAD_NEED_LOG;\n\n        /**\n         * just for cache Application's Context, and ':filedownloader' progress will NOT be launched\n         * by below code, so please do not worry about performance.\n         * @see FileDownloader#init(Context)\n         */\n        FileDownloader.setupOnApplicationOnCreate(this)\n                .connectionCreator(new FileDownloadUrlConnection\n                        .Creator(new FileDownloadUrlConnection.Configuration()\n                        .connectTimeout(15_000) // set connection timeout.\n                        .readTimeout(15_000) // set read timeout.\n                ))\n                .commit();\n\n        // below codes just for monitoring thread pools in the FileDownloader:\n        IThreadDebugger debugger = ThreadDebugger.install(\n                ThreadDebuggers.create() /** The ThreadDebugger with known thread Categories **/\n                        // add Thread Category\n                        .add(\"OkHttp\").add(\"okio\").add(\"Binder\")\n                        .add(FileDownloadUtils.getThreadPoolName(\"Network\"), \"Network\")\n                        .add(FileDownloadUtils.getThreadPoolName(\"Flow\"), \"FlowSingle\")\n                        .add(FileDownloadUtils.getThreadPoolName(\"EventPool\"), \"Event\")\n                        .add(FileDownloadUtils.getThreadPoolName(\"LauncherTask\"), \"LauncherTask\")\n                        .add(FileDownloadUtils.getThreadPoolName(\"ConnectionBlock\"), \"Connection\")\n                        .add(FileDownloadUtils.getThreadPoolName(\"RemitHandoverToDB\"), \"RemitHandoverToDB\")\n                        .add(FileDownloadUtils.getThreadPoolName(\"BlockCompleted\"), \"BlockCompleted\"),\n\n                2000, /** The frequent of Updating Thread Activity information **/\n\n                new ThreadDebugger.ThreadChangedCallback() {\n                    /**\n                     * The threads changed callback\n                     **/\n                    @Override\n                    public void onChanged(IThreadDebugger debugger) {\n                        // callback this method when the threads in this application has changed.\n                        Log.d(TAG, debugger.drawUpEachThreadInfoDiff());\n                        Log.d(TAG, debugger.drawUpEachThreadSizeDiff());\n                        Log.d(TAG, debugger.drawUpEachThreadSize());\n                    }\n                });\n    }\n}\n"
  },
  {
    "path": "demo/src/main/java/com/liulishuo/filedownloader/demo/GlobalMonitor.java",
    "content": "/*\n * Copyright (c) 2015 LingoChamp Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *    http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.liulishuo.filedownloader.demo;\n\nimport android.util.Log;\n\nimport com.liulishuo.filedownloader.BaseDownloadTask;\nimport com.liulishuo.filedownloader.FileDownloadListener;\nimport com.liulishuo.filedownloader.FileDownloadMonitor;\n\n/**\n * Created by Jacksgong on 1/19/16.\n */\npublic class GlobalMonitor implements FileDownloadMonitor.IMonitor {\n    private volatile int markStart;\n    private volatile int markOver;\n\n    private final static class HolderClass {\n        private final static GlobalMonitor INSTANCE = new GlobalMonitor();\n    }\n\n    public static GlobalMonitor getImpl() {\n        return HolderClass.INSTANCE;\n    }\n\n    private final static String TAG = \"GlobalMonitor\";\n\n    @Override\n    public void onRequestStart(int count, boolean serial, FileDownloadListener lis) {\n        markStart = 0;\n        markOver = 0;\n        Log.d(TAG, String.format(\"on request start %d %B\", count, serial));\n    }\n\n    @Override\n    public void onRequestStart(BaseDownloadTask task) {\n    }\n\n    @Override\n    public void onTaskBegin(BaseDownloadTask task) {\n        markStart++;\n    }\n\n    @Override\n    public void onTaskStarted(BaseDownloadTask task) {\n\n    }\n\n    @Override\n    public void onTaskOver(BaseDownloadTask task) {\n        markOver++;\n    }\n\n    public int getMarkStart() {\n        return markStart;\n    }\n\n    public int getMarkOver() {\n        return markOver;\n    }\n}\n"
  },
  {
    "path": "demo/src/main/java/com/liulishuo/filedownloader/demo/HybridTestActivity.java",
    "content": "package com.liulishuo.filedownloader.demo;\n\nimport android.os.Bundle;\nimport android.os.Handler;\nimport android.os.Message;\nimport android.support.v7.app.AppCompatActivity;\nimport android.text.Html;\nimport android.util.Log;\nimport android.view.MotionEvent;\nimport android.view.View;\nimport android.widget.LinearLayout;\nimport android.widget.ScrollView;\nimport android.widget.TextView;\n\nimport com.liulishuo.filedownloader.BaseDownloadTask;\nimport com.liulishuo.filedownloader.FileDownloadListener;\nimport com.liulishuo.filedownloader.FileDownloadQueueSet;\nimport com.liulishuo.filedownloader.FileDownloader;\nimport com.liulishuo.filedownloader.util.FileDownloadUtils;\n\nimport java.io.File;\nimport java.util.ArrayList;\nimport java.util.List;\n\n/**\n * Created by Jacksgong on 12/19/15.\n */\npublic class HybridTestActivity extends AppCompatActivity {\n\n    private final String TAG = \"Demo.HybridActivity\";\n    private Handler uiHandler;\n    private final int WHAT_NEED_AUTO_2_BOTTOM = 1;\n\n    @Override\n    protected void onCreate(Bundle savedInstanceState) {\n        super.onCreate(savedInstanceState);\n        setContentView(R.layout.activity_hybrid_test);\n\n        uiHandler = new Handler(new Handler.Callback() {\n            @Override\n            public boolean handleMessage(Message msg) {\n                if (msg.what == WHAT_NEED_AUTO_2_BOTTOM) {\n                    needAuto2Bottom = true;\n                }\n                return false;\n            }\n        });\n\n        assignViews();\n        scrollView.setOnTouchListener(new View.OnTouchListener() {\n            @Override\n            public boolean onTouch(View v, MotionEvent event) {\n                if (event.getAction() == MotionEvent.ACTION_DOWN) {\n                    uiHandler.removeMessages(WHAT_NEED_AUTO_2_BOTTOM);\n                    needAuto2Bottom = false;\n                }\n\n                if (event.getAction() == MotionEvent.ACTION_UP ||\n                        event.getAction() == MotionEvent.ACTION_CANCEL) {\n                    uiHandler.removeMessages(WHAT_NEED_AUTO_2_BOTTOM);\n                    uiHandler.sendEmptyMessageDelayed(WHAT_NEED_AUTO_2_BOTTOM, 1000);\n                }\n\n                return false;\n            }\n        });\n    }\n\n    private boolean needAuto2Bottom = true;\n\n    public void onClickDel(final View view) {\n        File file = new File(FileDownloadUtils.getDefaultSaveRootPath());\n        if (!file.exists()) {\n            Log.w(TAG, String.format(\"check file files not exists %s\", file.getAbsolutePath()));\n            return;\n        }\n\n        if (!file.isDirectory()) {\n            Log.w(TAG, String.format(\"check file files not directory %s\", file.getAbsolutePath()));\n            return;\n        }\n\n        File[] files = file.listFiles();\n\n        if (files == null) {\n            updateDisplay(getString(R.string.del_file_error_empty));\n            return;\n        }\n\n        for (File file1 : files) {\n            file1.delete();\n            updateDisplay(getString(R.string.hybrid_test_deleted_file, file1.getName()));\n        }\n    }\n\n    private int totalCounts = 0;\n    private int finalCounts = 0;\n\n    // =================================================== demo area ========================================================\n\n    /**\n     * Start single download task\n     * <p>\n     * 启动单任务下载\n     *\n     * @param view\n     */\n    public void onClickStartSingleDownload(final View view) {\n        updateDisplay(getString(R.string.hybrid_test_start_single_task, Constant.BIG_FILE_URLS[2]));\n        totalCounts++;\n        FileDownloader.getImpl().create(Constant.BIG_FILE_URLS[2])\n                .setListener(createListener())\n                .setTag(1)\n                .start();\n    }\n\n    /**\n     * Start multiple download tasks parallel\n     * <p>\n     * 启动并行多任务下载\n     *\n     * @param view\n     */\n    public void onClickMultiParallel(final View view) {\n        updateDisplay(getString(R.string.hybrid_test_start_multiple_tasks_parallel, Constant.URLS.length));\n\n        // 以相同的listener作为target，将不同的下载任务绑定起来\n        final FileDownloadListener parallelTarget = createListener();\n        final List<BaseDownloadTask> taskList = new ArrayList<>();\n        int i = 0;\n        for (String url : Constant.URLS) {\n            taskList.add(FileDownloader.getImpl().create(url)\n                    .setTag(++i));\n        }\n        totalCounts += taskList.size();\n\n        new FileDownloadQueueSet(parallelTarget)\n                .setCallbackProgressTimes(1)\n                .downloadTogether(taskList)\n                .start();\n    }\n\n    /**\n     * Start multiple download tasks serial\n     * <p>\n     * 启动串行多任务下载\n     *\n     * @param view\n     */\n    public void onClickMultiSerial(final View view) {\n        updateDisplay(getString(R.string.hybrid_test_start_multiple_tasks_serial, Constant.URLS.length));\n\n        // 以相同的listener作为target，将不同的下载任务绑定起来\n        final List<BaseDownloadTask> taskList = new ArrayList<>();\n        final FileDownloadListener serialTarget = createListener();\n        int i = 0;\n        for (String url : Constant.URLS) {\n            taskList.add(FileDownloader.getImpl().create(url)\n                    .setTag(++i));\n        }\n        totalCounts += taskList.size();\n\n        new FileDownloadQueueSet(serialTarget)\n                .setCallbackProgressTimes(1)\n                .downloadSequentially(taskList)\n                .start();\n    }\n\n    private FileDownloadListener createListener() {\n        return new FileDownloadListener() {\n\n            @Override\n            protected boolean isInvalid() {\n                return isFinishing();\n            }\n\n            @Override\n            protected void pending(final BaseDownloadTask task, final int soFarBytes, final int totalBytes) {\n                updateDisplay(String.format(\"[pending] id[%d] %d/%d\", task.getId(), soFarBytes, totalBytes));\n            }\n\n            @Override\n            protected void connected(BaseDownloadTask task, String etag, boolean isContinue, int soFarBytes, int totalBytes) {\n                super.connected(task, etag, isContinue, soFarBytes, totalBytes);\n                updateDisplay(String.format(\"[connected] id[%d] %s %B %d/%d\", task.getId(), etag, isContinue, soFarBytes, totalBytes));\n            }\n\n            @Override\n            protected void progress(final BaseDownloadTask task, final int soFarBytes, final int totalBytes) {\n                updateDisplay(String.format(\"[progress] id[%d] %d/%d\", task.getId(), soFarBytes, totalBytes));\n            }\n\n            @Override\n            protected void blockComplete(final BaseDownloadTask task) {\n                downloadMsgTv.post(new Runnable() {\n                    @Override\n                    public void run() {\n                        updateDisplay(String.format(\"[blockComplete] id[%d]\", task.getId()));\n                    }\n                });\n            }\n\n            @Override\n            protected void retry(BaseDownloadTask task, Throwable ex, int retryingTimes, int soFarBytes) {\n                super.retry(task, ex, retryingTimes, soFarBytes);\n                updateDisplay(String.format(\"[retry] id[%d] %s %d %d\",\n                        task.getId(), ex, retryingTimes, soFarBytes));\n            }\n\n            @Override\n            protected void completed(BaseDownloadTask task) {\n                finalCounts++;\n                updateDisplay(String.format(\"[completed] id[%d] oldFile[%B]\",\n                        task.getId(),\n                        task.isReusedOldFile()));\n                updateDisplay(String.format(\"---------------------------------- %d\", (Integer) task.getTag()));\n            }\n\n            @Override\n            protected void paused(final BaseDownloadTask task, final int soFarBytes, final int totalBytes) {\n                finalCounts++;\n                updateDisplay(String.format(\"[paused] id[%d] %d/%d\", task.getId(), soFarBytes, totalBytes));\n                updateDisplay(String.format(\"############################## %d\", (Integer) task.getTag()));\n            }\n\n            @Override\n            protected void error(BaseDownloadTask task, Throwable e) {\n                finalCounts++;\n                updateDisplay(Html.fromHtml(String.format(\"[error] id[%d] %s %s\",\n                        task.getId(),\n                        e,\n                        FileDownloadUtils.getStack(e.getStackTrace(), false))));\n\n                updateDisplay(String.format(\"!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! %d\", (Integer) task.getTag()));\n            }\n\n            @Override\n            protected void warn(BaseDownloadTask task) {\n                finalCounts++;\n                updateDisplay(String.format(\"[warn] id[%d]\", task.getId()));\n                updateDisplay(String.format(\"^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ %d\", (Integer) task.getTag()));\n            }\n        };\n    }\n\n    // -------------------------------------------------------- something just for display ------------------------------------------------------\n\n    private void updateDisplay(final CharSequence msg) {\n        if (downloadMsgTv.getLineCount() > 2500) {\n            downloadMsgTv.setText(\"\");\n        }\n        downloadMsgTv.append(String.format(\"\\n %s\", msg));\n        tipMsgTv.setText(String.format(\"%d/%d\", finalCounts, totalCounts));\n        if (needAuto2Bottom) {\n            scrollView.post(scroll2Bottom);\n        }\n    }\n\n    private Runnable scroll2Bottom = new Runnable() {\n        @Override\n        public void run() {\n            if (scrollView != null) {\n                scrollView.fullScroll(View.FOCUS_DOWN);\n            }\n        }\n    };\n\n    @Override\n    protected void onDestroy() {\n        super.onDestroy();\n        FileDownloader.getImpl().pauseAll();\n    }\n\n    private LinearLayout topGroup;\n    private ScrollView scrollView;\n    private TextView downloadMsgTv;\n    private TextView tipMsgTv;\n\n    private void assignViews() {\n        topGroup = (LinearLayout) findViewById(R.id.top_group);\n        scrollView = (ScrollView) findViewById(R.id.scrollView);\n        downloadMsgTv = (TextView) findViewById(R.id.download_msg_tv);\n        tipMsgTv = (TextView) findViewById(R.id.tip_msg_tv);\n    }\n\n}\n"
  },
  {
    "path": "demo/src/main/java/com/liulishuo/filedownloader/demo/MainActivity.java",
    "content": "package com.liulishuo.filedownloader.demo;\n\nimport android.content.Intent;\nimport android.net.Uri;\nimport android.os.Bundle;\nimport android.support.v7.app.AppCompatActivity;\nimport android.view.Menu;\nimport android.view.MenuItem;\nimport android.view.View;\n\nimport com.liulishuo.filedownloader.FileDownloadMonitor;\nimport com.liulishuo.filedownloader.FileDownloader;\nimport com.liulishuo.filedownloader.demo.performance.PerformanceTestActivity;\n\n/**\n * Created by Jacksgong on 12/17/15.\n */\npublic class MainActivity extends AppCompatActivity {\n\n    @Override\n    protected void onCreate(Bundle savedInstanceState) {\n        super.onCreate(savedInstanceState);\n\n        setContentView(R.layout.activity_main);\n\n        // 这是只是为了全局监控。如果你有需求需要全局监控（比如用于打点/统计）可以使用这个方式，如果没有类似需求就不需要\n        // 如果你有这个需求，实现FileDownloadMonitor.IMonitor接口，也使用FileDownloadMonitor.setGlobalMonitor\n        // 注册进去即可\n        // You do not have to add below code to your project only if you need monitor the global\n        // FileDownloader Engine for statistic or others\n        // If you have such requirement, just implement FileDownloadMonitor.IMonitor, and register it\n        // use FileDownloadDownloader.setGlobalMonitor the same as below code.\n        FileDownloadMonitor.setGlobalMonitor(GlobalMonitor.getImpl());\n    }\n\n    public void onClickMultitask(final View view) {\n        startActivity(new Intent(this, MultitaskTestActivity.class));\n    }\n\n    public void onClickSingle(final View view) {\n        startActivity(new Intent(this, SingleTaskTestActivity.class));\n    }\n\n    public void onClickHybridTest(final View view) {\n        startActivity(new Intent(this, HybridTestActivity.class));\n    }\n\n    public void onClickTasksManager(final View view) {\n        startActivity(new Intent(this, TasksManagerDemoActivity.class));\n    }\n\n    public void onClickPerformance(final View view) {\n        startActivity(new Intent(this, PerformanceTestActivity.class));\n    }\n\n    public void onClickNotification(final View view){\n        startActivity(new Intent(this, NotificationSampleActivity.class));\n    }\n\n    @Override\n    public boolean onCreateOptionsMenu(Menu menu) {\n        getMenuInflater().inflate(R.menu.menu_main, menu);\n        return super.onCreateOptionsMenu(menu);\n    }\n\n\n    @Override\n    public boolean onOptionsItemSelected(MenuItem item) {\n        switch (item.getItemId()) {\n            case R.id.menu_github:\n                openGitHub();\n                return true;\n            default:\n                return super.onOptionsItemSelected(item);\n        }\n    }\n\n    private void openGitHub() {\n        Uri uri = Uri.parse(getString(R.string.app_github_url));\n        Intent intent = new Intent(Intent.ACTION_VIEW, uri);\n        startActivity(intent);\n    }\n\n    @Override\n    protected void onDestroy() {\n        super.onDestroy();\n        // unbind and stop service manually if idle\n        FileDownloader.getImpl().unBindServiceIfIdle();\n\n        FileDownloadMonitor.releaseGlobalMonitor();\n    }\n}\n"
  },
  {
    "path": "demo/src/main/java/com/liulishuo/filedownloader/demo/MultitaskTestActivity.java",
    "content": "/*\n * Copyright (c) 2015 LingoChamp Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *    http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.liulishuo.filedownloader.demo;\n\nimport android.os.Bundle;\nimport android.support.v7.app.AppCompatActivity;\nimport android.util.Log;\nimport android.view.View;\nimport android.widget.Button;\nimport android.widget.CheckBox;\nimport android.widget.CompoundButton;\nimport android.widget.ProgressBar;\nimport android.widget.RadioButton;\nimport android.widget.RadioGroup;\nimport android.widget.SeekBar;\nimport android.widget.TextView;\nimport android.widget.Toast;\n\nimport com.liulishuo.filedownloader.BaseDownloadTask;\nimport com.liulishuo.filedownloader.FileDownloadListener;\nimport com.liulishuo.filedownloader.FileDownloadQueueSet;\nimport com.liulishuo.filedownloader.FileDownloader;\nimport com.liulishuo.filedownloader.util.FileDownloadUtils;\n\nimport java.io.File;\nimport java.util.ArrayList;\nimport java.util.List;\n\n/**\n * Created by Jacksgong on 12/25/15.\n */\npublic class MultitaskTestActivity extends AppCompatActivity {\n\n    private final static String TAG = \"MultitaskTestActivity\";\n\n    @Override\n    protected void onDestroy() {\n        super.onDestroy();\n        pause();\n    }\n\n    @Override\n    protected void onCreate(Bundle savedInstanceState) {\n        super.onCreate(savedInstanceState);\n        setContentView(R.layout.activity_mutitask_test);\n        assignViews();\n        resetDisplayData();\n\n        actionBtn.setTag(true);\n        actionBtn.setOnClickListener(new View.OnClickListener() {\n            @Override\n            public void onClick(View v) {\n                final boolean toStart = (boolean) actionBtn.getTag();\n                if (toStart) {\n                    if (start()) {\n                        actionBtn.setText(R.string.pause);\n                        actionBtn.setTag(false);\n                    }\n                } else {\n                    actionBtn.setText(R.string.start);\n                    pause();\n                    actionBtn.setTag(true);\n                }\n\n            }\n        });\n\n        taskCountSb.setMax(Constant.URLS.length);\n        taskCountSb.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() {\n            @Override\n            public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {\n                taskCountTv.setText(String.valueOf(progress));\n            }\n\n            @Override\n            public void onStartTrackingTouch(SeekBar seekBar) {\n            }\n\n            @Override\n            public void onStopTrackingTouch(SeekBar seekBar) {\n            }\n        });\n\n        deleteAllFileBtn.setOnClickListener(new View.OnClickListener() {\n            @Override\n            public void onClick(View v) {\n                int count = 0;\n                File file = new File(FileDownloadUtils.getDefaultSaveRootPath());\n                do {\n                    if (!file.exists()) {\n                        break;\n                    }\n\n                    if (!file.isDirectory()) {\n                        break;\n                    }\n\n                    File[] files = file.listFiles();\n\n                    if (files == null) {\n                        break;\n                    }\n\n                    for (File file1 : files) {\n                        count++;\n                        file1.delete();\n                    }\n\n                } while (false);\n\n                Toast.makeText(MultitaskTestActivity.this,\n                        String.format(\"Complete delete %d files\", count), Toast.LENGTH_LONG).show();\n\n            }\n        });\n\n        avoidMissFrameCb.setChecked(FileDownloader.isEnabledAvoidDropFrame());\n        avoidMissFrameCb.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {\n            @Override\n            public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {\n                if (isChecked) {\n                    FileDownloader.enableAvoidDropFrame();\n                } else {\n                    FileDownloader.disableAvoidDropFrame();\n                }\n            }\n        });\n    }\n\n    private void resetDisplayData() {\n        pendingPb.setProgress(0);\n        connectedPb.setProgress(0);\n        progressPb.setProgress(0);\n        retryPb.setProgress(0);\n        errorPb.setProgress(0);\n        pausedPb.setProgress(0);\n        completedReusedPb.setProgress(0);\n        completedDownloadingPb.setProgress(0);\n        warnPb.setProgress(0);\n        overTaskPb.setProgress(0);\n\n        pendingTv.setText(getString(R.string.multitask_test_pending, 0));\n        connectedTv.setText(getString(R.string.multitask_test_connected, 0));\n        progressTv.setText(getString(R.string.multitask_test_progress, 0));\n        retryTv.setText(getString(R.string.multitask_test_retry, 0));\n        errorTv.setText(getString(R.string.multitask_test_error, 0));\n        pausedTv.setText(getString(R.string.multitask_test_paused, 0));\n        completedReusedTv.setText(getString(R.string.multitask_test_completed_reused, 0));\n        completedDownloadingTv.setText(getString(R.string.multitask_test_completed_downloading, 0));\n        warnTv.setText(getString(R.string.multitask_test_warn, 0));\n\n        pendingInfoTv.setText(\"\");\n        connectedInfoTv.setText(\"\");\n        retryInfoTv.setText(\"\");\n        progressInfoTv.setText(\"\");\n        errorInfoTv.setText(\"\");\n        pausedInfoTv.setText(\"\");\n        completedReusedInfoTv.setText(\"\");\n        completedDownloadingInfoTv.setText(\"\");\n        warnInfoTv.setText(\"\");\n    }\n\n    private long start = 0;\n\n    private boolean start() {\n        final int count = Integer.valueOf(taskCountTv.getText().toString());\n        if (count <= 0) {\n            return false;\n        }\n\n        taskCountSb.setEnabled(false);\n\n        pendingPb.setMax(count);\n        connectedPb.setMax(count);\n        progressPb.setMax(count);\n        retryPb.setMax(count);\n        errorPb.setMax(count);\n        pausedPb.setMax(count);\n        completedReusedPb.setMax(count);\n        completedDownloadingPb.setMax(count);\n        warnPb.setMax(count);\n        overTaskPb.setMax(count);\n\n\n        resetDisplayData();\n\n        // 需要时再显示\n        retryInfoTv.setVisibility(View.GONE);\n        retryPb.setVisibility(View.GONE);\n        retryTv.setVisibility(View.GONE);\n\n        isStopTimer = false;\n        timeConsumeTv.setTag(0);\n        goTimeCount();\n\n\n        start = System.currentTimeMillis();\n        // =================== How to Download tasks: =============================\n        downloadListener = createLis();\n//        The first way-----------------------------:\n//        for (int i = 0; i < count; i++) {\n//            final String url = Constant.URLS[i];\n//            FileDownloader.getImpl().create(url)\n//                    .setListener(downloadListener)\n//                    .setAutoRetryTimes(1)\n//                    .setTag(i + 1)\n//                    .setCallbackProgressTimes(0)\n//                    .asInQueueTask()\n//                    .enqueue();\n//        }\n//        FileDownloader.getImpl().start(downloadListener, serialRbtn.isChecked());\n\n//        The second way----------------------------:\n\n        final FileDownloadQueueSet queueSet = new FileDownloadQueueSet(downloadListener);\n\n        final List<BaseDownloadTask> tasks = new ArrayList<>();\n        for (int i = 0; i < count; i++) {\n            tasks.add(FileDownloader.getImpl().create(Constant.URLS[i]).setTag(i + 1));\n        }\n        queueSet.disableCallbackProgressTimes(); // do not want each task's download progress's callback,\n        // we just consider which task will completed.\n\n        // auto retry 1 time if download fail\n        queueSet.setAutoRetryTimes(1);\n\n        if (serialRbtn.isChecked()) {\n            // start download in serial order\n            queueSet.downloadSequentially(tasks);\n            // if your tasks are not a list, invoke such following will more readable:\n//            queueSet.downloadSequentially(\n//                    FileDownloader.getImpl().create(url).setPath(...),\n//                    FileDownloader.getImpl().create(url).addHeader(...,...),\n//                    FileDownloader.getImpl().create(url).setPath(...)\n//            );\n        } else {\n            // start parallel download\n            queueSet.downloadTogether(tasks);\n            // if your tasks are not a list, invoke such following will more readable:\n//            queueSet.downloadTogether(\n//                    FileDownloader.getImpl().create(url).setPath(...),\n//                    FileDownloader.getImpl().create(url).setPath(...),\n//                    FileDownloader.getImpl().create(url).setSyncCallback(true)\n//            );\n        }\n        queueSet.start();\n\n        return true;\n    }\n\n    private FileDownloadListener downloadListener;\n\n    private void pause() {\n        FileDownloader.getImpl().pause(downloadListener);\n        stopTimeCount();\n        taskCountSb.setEnabled(true);\n    }\n\n    private void stopTimeCount() {\n        if (isFinishing()) {\n            return;\n        }\n\n        isStopTimer = true;\n        timeConsumeTv.getHandler().removeCallbacks(timeCountRunnable);\n        final long consume = System.currentTimeMillis() - start;\n        if (timeConsumeTv != null) {\n            timeConsumeTv.setText(String.valueOf(consume / 1000f));\n        }\n    }\n\n    private FileDownloadListener createLis() {\n        return new FileDownloadListener() {\n\n            @Override\n            protected void pending(BaseDownloadTask task, int soFarBytes, int totalBytes) {\n                // 之所以加这句判断，是因为有些异步任务在pause以后，会持续回调pause回来，而有些任务在pause之前已经完成，\n                // 但是通知消息还在线程池中还未回调回来，这里可以优化\n                // 后面所有在回调中加这句都是这个原因\n                if (task.getListener() != downloadListener) {\n                    return;\n                }\n                pendingPb.setProgress(pendingPb.getProgress() + 1);\n                pendingTv.setText(getString(R.string.multitask_test_pending, pendingPb.getProgress()));\n                pendingInfoTv.append((int) task.getTag() + \" | \");\n            }\n\n            @Override\n            protected void connected(BaseDownloadTask task, String etag, boolean isContinue,\n                                     int soFarBytes, int totalBytes) {\n\n                super.connected(task, etag, isContinue, soFarBytes, totalBytes);\n                if (task.getListener() != downloadListener) {\n                    return;\n                }\n\n                connectedPb.setProgress(connectedPb.getProgress() + 1);\n                connectedTv.setText(getString(R.string.multitask_test_connected,\n                        connectedPb.getProgress()));\n\n                connectedInfoTv.append((int) task.getTag() + \" | \");\n            }\n\n            @Override\n            protected void progress(BaseDownloadTask task, int soFarBytes, int totalBytes) {\n                if (task.getListener() != downloadListener) {\n                    return;\n                }\n//                progressPb.setProgress(progressPb.getProgress() + 1);\n//                progressTv.setText(\"progress: \" + progressPb.getProgress());\n//                progressInfoTv.append((int)task.getTag() + \" | \");\n            }\n\n            @Override\n            protected void blockComplete(BaseDownloadTask task) {\n                if (task.getListener() != downloadListener) {\n                    return;\n                }\n            }\n\n            @Override\n            protected void retry(BaseDownloadTask task, Throwable ex, int retryingTimes, int soFarBytes) {\n                super.retry(task, ex, retryingTimes, soFarBytes);\n                if (task.getListener() != downloadListener) {\n                    return;\n                }\n\n                retryInfoTv.setVisibility(View.VISIBLE);\n                retryPb.setVisibility(View.VISIBLE);\n                retryTv.setVisibility(View.VISIBLE);\n\n                retryPb.setProgress(retryPb.getProgress() + 1);\n                retryTv.setText(getString(R.string.multitask_test_retry, retryPb.getProgress()));\n                retryInfoTv.append((int)task.getTag() + \" | \");\n            }\n\n            @Override\n            protected void completed(BaseDownloadTask task) {\n                if (task.getListener() != downloadListener) {\n                    return;\n                }\n\n                if (task.isReusedOldFile()) {\n                    completedReusedPb.setProgress(completedReusedPb.getProgress() + 1);\n                    completedReusedTv.setText(getString(R.string.multitask_test_completed_reused,\n                            completedReusedPb.getProgress()));\n                    completedReusedInfoTv.append((int) task.getTag() + \" | \");\n                } else {\n                    completedDownloadingPb.setProgress(completedDownloadingPb.getProgress() + 1);\n                    completedDownloadingTv.\n                            setText(getString(R.string.multitask_test_completed_downloading,\n                                    completedDownloadingPb.getProgress()));\n\n                    completedDownloadingInfoTv.append((int) task.getTag() + \" | \");\n                }\n\n                overTaskPb.setProgress(overTaskPb.getProgress() + 1);\n                checkEndAll();\n            }\n\n            @Override\n            protected void paused(BaseDownloadTask task, int soFarBytes, int totalBytes) {\n                if (task.getListener() != downloadListener) {\n                    return;\n                }\n                pausedPb.setProgress(pausedPb.getProgress() + 1);\n                pausedTv.setText(getString(R.string.multitask_test_paused, pausedPb.getProgress()));\n                pausedInfoTv.append((int) task.getTag() + \" | \");\n                overTaskPb.setProgress(overTaskPb.getProgress() + 1);\n            }\n\n            @Override\n            protected void error(BaseDownloadTask task, Throwable e) {\n                if (task.getListener() != downloadListener) {\n                    return;\n                }\n                errorPb.setProgress(errorPb.getProgress() + 1);\n                errorTv.setText(getString(R.string.multitask_test_error, errorPb.getProgress()));\n                errorInfoTv.append((int) task.getTag() + \" | \");\n                overTaskPb.setProgress(overTaskPb.getProgress() + 1);\n                checkEndAll();\n            }\n\n            @Override\n            protected void warn(BaseDownloadTask task) {\n                if (task.getListener() != downloadListener) {\n                    return;\n                }\n\n                warnPb.setProgress(warnPb.getProgress() + 1);\n                warnTv.setText(getString(R.string.multitask_test_warn, warnPb.getProgress()));\n                warnInfoTv.append((int) task.getTag() + \" | \");\n                overTaskPb.setProgress(overTaskPb.getProgress() + 1);\n                checkEndAll();\n            }\n        };\n    }\n\n    private void checkEndAll() {\n        final boolean isEndAll = overTaskPb.getProgress() >= Integer.valueOf(taskCountTv.getText().toString());\n        if (isEndAll) {\n\n            Log.d(TAG, String.format(\"start[%d] over[%d]\", GlobalMonitor.getImpl().getMarkStart(),\n                    GlobalMonitor.getImpl().getMarkOver()));\n\n            stopTimeCount();\n            actionBtn.setTag(true);\n            actionBtn.setText(\"Start\");\n            taskCountSb.setEnabled(true);\n        }\n    }\n\n    private boolean isStopTimer = true;\n\n    private void goTimeCount() {\n        if (isFinishing()) {\n            return;\n        }\n        final int time = (int) timeConsumeTv.getTag();\n        timeConsumeTv.setText(String.valueOf(time));\n        timeConsumeTv.getHandler().postDelayed(timeCountRunnable, 1000);\n    }\n\n    private Runnable timeCountRunnable = new Runnable() {\n        @Override\n        public void run() {\n            if (isStopTimer) {\n                return;\n            }\n            timeConsumeTv.setTag((int)timeConsumeTv.getTag() + 1);\n            goTimeCount();\n        }\n    };\n\n    private SeekBar taskCountSb;\n    private TextView taskCountTv;\n    private TextView timeConsumeTv;\n    private RadioGroup wayRgp;\n    private RadioButton serialRbtn;\n    private RadioButton parallelRbtn;\n    private CheckBox avoidMissFrameCb;\n    private ProgressBar overTaskPb;\n    private Button actionBtn;\n    private TextView pendingTv;\n    private TextView pendingInfoTv;\n    private ProgressBar pendingPb;\n    private TextView connectedTv;\n    private TextView connectedInfoTv;\n    private ProgressBar connectedPb;\n    private TextView progressTv;\n    private TextView progressInfoTv;\n    private ProgressBar progressPb;\n    private TextView retryTv;\n    private TextView retryInfoTv;\n    private ProgressBar retryPb;\n    private TextView errorTv;\n    private TextView errorInfoTv;\n    private ProgressBar errorPb;\n    private TextView pausedTv;\n    private TextView pausedInfoTv;\n    private ProgressBar pausedPb;\n    private TextView completedReusedTv;\n    private TextView completedReusedInfoTv;\n    private ProgressBar completedReusedPb;\n    private TextView completedDownloadingTv;\n    private TextView completedDownloadingInfoTv;\n    private ProgressBar completedDownloadingPb;\n    private TextView warnTv;\n    private TextView warnInfoTv;\n    private ProgressBar warnPb;\n    private Button deleteAllFileBtn;\n\n    private void assignViews() {\n        taskCountSb = (SeekBar) findViewById(R.id.task_count_sb);\n        taskCountTv = (TextView) findViewById(R.id.task_count_tv);\n        timeConsumeTv = (TextView) findViewById(R.id.time_consume_tv);\n        wayRgp = (RadioGroup) findViewById(R.id.way_rgp);\n        serialRbtn = (RadioButton) findViewById(R.id.serial_rbtn);\n        parallelRbtn = (RadioButton) findViewById(R.id.parallel_rbtn);\n        avoidMissFrameCb = (CheckBox) findViewById(R.id.avoid_miss_frame_cb);\n        overTaskPb = (ProgressBar) findViewById(R.id.over_task_pb);\n        actionBtn = (Button) findViewById(R.id.action_btn);\n        pendingTv = (TextView) findViewById(R.id.pending_tv);\n        pendingInfoTv = (TextView) findViewById(R.id.pending_info_tv);\n        pendingPb = (ProgressBar) findViewById(R.id.pending_pb);\n        connectedTv = (TextView) findViewById(R.id.connected_tv);\n        connectedInfoTv = (TextView) findViewById(R.id.connected_info_tv);\n        connectedPb = (ProgressBar) findViewById(R.id.connected_pb);\n        progressTv = (TextView) findViewById(R.id.progress_tv);\n        progressInfoTv = (TextView) findViewById(R.id.progress_info_tv);\n        progressPb = (ProgressBar) findViewById(R.id.progress_pb);\n        retryTv = (TextView) findViewById(R.id.retry_tv);\n        retryInfoTv = (TextView) findViewById(R.id.retry_info_tv);\n        retryPb = (ProgressBar) findViewById(R.id.retry_pb);\n        errorTv = (TextView) findViewById(R.id.error_tv);\n        errorInfoTv = (TextView) findViewById(R.id.error_info_tv);\n        errorPb = (ProgressBar) findViewById(R.id.error_pb);\n        pausedTv = (TextView) findViewById(R.id.paused_tv);\n        pausedInfoTv = (TextView) findViewById(R.id.paused_info_tv);\n        pausedPb = (ProgressBar) findViewById(R.id.paused_pb);\n        completedReusedTv = (TextView) findViewById(R.id.completed_with_old_tv);\n        completedReusedInfoTv = (TextView) findViewById(R.id.completed_with_old_info_tv);\n        completedReusedPb = (ProgressBar) findViewById(R.id.completed_with_old_pb);\n        completedDownloadingTv = (TextView) findViewById(R.id.completed_tv);\n        completedDownloadingInfoTv = (TextView) findViewById(R.id.completed_info_tv);\n        completedDownloadingPb = (ProgressBar) findViewById(R.id.completed_pb);\n        warnTv = (TextView) findViewById(R.id.warn_tv);\n        warnInfoTv = (TextView) findViewById(R.id.warn_info_tv);\n        warnPb = (ProgressBar) findViewById(R.id.warn_pb);\n        deleteAllFileBtn = (Button) findViewById(R.id.delete_all_file_btn);\n    }\n\n\n}\n"
  },
  {
    "path": "demo/src/main/java/com/liulishuo/filedownloader/demo/NotificationSampleActivity.java",
    "content": "/*\n * Copyright (c) 2015 LingoChamp Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *    http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.liulishuo.filedownloader.demo;\n\nimport android.app.Notification;\nimport android.app.PendingIntent;\nimport android.content.ComponentName;\nimport android.content.Intent;\nimport android.os.Build;\nimport android.os.Bundle;\nimport android.support.v4.app.NotificationCompat;\nimport android.support.v7.app.AppCompatActivity;\nimport android.view.View;\nimport android.widget.CheckBox;\nimport android.widget.ProgressBar;\n\nimport com.liulishuo.filedownloader.BaseDownloadTask;\nimport com.liulishuo.filedownloader.FileDownloadQueueSet;\nimport com.liulishuo.filedownloader.FileDownloader;\nimport com.liulishuo.filedownloader.model.FileDownloadStatus;\nimport com.liulishuo.filedownloader.notification.BaseNotificationItem;\nimport com.liulishuo.filedownloader.notification.FileDownloadNotificationHelper;\nimport com.liulishuo.filedownloader.notification.FileDownloadNotificationListener;\nimport com.liulishuo.filedownloader.util.FileDownloadHelper;\nimport com.liulishuo.filedownloader.util.FileDownloadUtils;\n\nimport java.io.File;\nimport java.lang.ref.WeakReference;\nimport java.util.ArrayList;\nimport java.util.List;\nimport java.util.concurrent.atomic.AtomicInteger;\n\n/**\n * Created by Jacksgong on 2/2/16.\n */\npublic class NotificationSampleActivity extends AppCompatActivity {\n    private final FileDownloadNotificationHelper<NotificationItem> notificationHelper =\n            new FileDownloadNotificationHelper<>();\n    private NotificationListener listener;\n\n    private final String savePath = FileDownloadUtils.getDefaultSaveRootPath() + File.separator + \"notification\";\n    private final String url = Constant.LIULISHUO_APK_URL;\n    private final String channelId = \"filedownloader_channel\";\n\n    @Override\n    protected void onCreate(Bundle savedInstanceState) {\n        super.onCreate(savedInstanceState);\n        setContentView(R.layout.activity_notification_sample);\n\n        assignViews();\n\n        listener = new NotificationListener(new WeakReference<>(this), channelId);\n        Utils.createNotificationChannel(channelId, \"Filedownloader\", getApplicationContext());\n    }\n\n\n    public void onClickStart(final View view) {\n        startDownload.setEnabled(false);\n        final FileDownloadQueueSet queueSet = new FileDownloadQueueSet(listener);\n        final List<BaseDownloadTask> tasks = new ArrayList<>();\n        final int taskCount = 5;\n        for (int i = 0; i < taskCount; i++) {\n            tasks.add(FileDownloader.getImpl()\n                    .create(Constant.URLS[i])\n                    .setTag(i + 1)\n                    .setPath(savePath, true)\n            );\n        }\n        queueSet.downloadTogether(tasks)\n                .addTaskFinishListener(new BaseDownloadTask.FinishListener() {\n                    final AtomicInteger counter = new AtomicInteger();\n\n                    @Override\n                    public void over(BaseDownloadTask task) {\n                        final int finishCount = counter.addAndGet(1);\n                        if (finishCount == taskCount) startDownload.post(new Runnable() {\n                            @Override\n                            public void run() {\n                                startDownload.setEnabled(true);\n                                progressBar.setIndeterminate(false);\n                                progressBar.setProgress(1);\n                                progressBar.setMax(1);\n                            }\n                        });\n                    }\n                })\n                .start();\n        progressBar.setIndeterminate(true);\n    }\n\n    public void onClickPause(final View view) {\n        FileDownloader.getImpl().pause(listener);\n    }\n\n    @SuppressWarnings(\"ResultOfMethodCallIgnored\")\n    public void onClickDelete(final View view) {\n        FileDownloader.getImpl().pause(listener);\n        new Thread(new Runnable() {\n            @Override\n            public void run() {\n                final File file = new File(savePath);\n                if (file.isFile()) file.delete();\n                if (file.exists()) {\n                    final File[] files = file.listFiles();\n                    if (files == null) return;\n                    for (File f : files) {\n                        f.delete();\n                    }\n                    file.delete();\n                }\n            }\n        }).start();\n    }\n\n    private static class NotificationListener extends FileDownloadNotificationListener {\n\n        private final String channelId;\n        private final WeakReference<NotificationSampleActivity> wActivity;\n\n        public NotificationListener(\n                WeakReference<NotificationSampleActivity> wActivity,\n                String channelId\n        ) {\n            super(wActivity.get().notificationHelper);\n            this.wActivity = wActivity;\n            this.channelId = channelId;\n        }\n\n        @Override\n        protected BaseNotificationItem create(BaseDownloadTask task) {\n            return new NotificationItem(\n                    task.getId(),\n                    \"Task-\" + task.getId(),\n                    \"\",\n                    channelId\n            );\n        }\n\n        @Override\n        public void addNotificationItem(BaseDownloadTask task) {\n            super.addNotificationItem(task);\n            if (wActivity.get() != null) {\n                wActivity.get().showNotificationCb.setEnabled(false);\n            }\n        }\n\n        @Override\n        public void destroyNotification(BaseDownloadTask task) {\n            super.destroyNotification(task);\n            if (wActivity.get() != null) {\n                wActivity.get().showNotificationCb.setEnabled(true);\n            }\n        }\n\n        @Override\n        protected boolean interceptCancel(BaseDownloadTask task,\n                                          BaseNotificationItem n) {\n            // in this demo, I don't want to cancel the notification, just show for the test\n            // so return true\n            return true;\n        }\n\n        @Override\n        protected boolean disableNotification(BaseDownloadTask task) {\n            if (wActivity.get() != null) {\n                return !wActivity.get().showNotificationCb.isChecked();\n            }\n\n            return super.disableNotification(task);\n        }\n    }\n\n    public static class NotificationItem extends BaseNotificationItem {\n\n        private final NotificationCompat.Builder builder;\n\n        private NotificationItem(int id, String title, String desc, String channelId) {\n            super(id, title, desc);\n            final Intent[] intents = new Intent[2];\n            intents[0] = Intent.makeMainActivity(\n                    new ComponentName(DemoApplication.CONTEXT, MainActivity.class));\n            intents[1] = new Intent(DemoApplication.CONTEXT, NotificationSampleActivity.class);\n            final PendingIntent pendingIntent = PendingIntent.getActivities(\n                    DemoApplication.CONTEXT, 0, intents,\n                    PendingIntent.FLAG_UPDATE_CURRENT);\n\n            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {\n                builder = new NotificationCompat.Builder(\n                        FileDownloadHelper.getAppContext(),\n                        channelId);\n            } else {\n                //noinspection deprecation\n                builder = new NotificationCompat.Builder(FileDownloadHelper.getAppContext())\n                        .setDefaults(Notification.DEFAULT_LIGHTS)\n                        .setPriority(NotificationCompat.PRIORITY_MIN);\n            }\n\n            builder.setContentTitle(getTitle())\n                    .setContentText(desc)\n                    .setContentIntent(pendingIntent)\n                    .setSmallIcon(R.mipmap.ic_launcher);\n        }\n\n        @Override\n        public void show(boolean statusChanged, int status, boolean isShowProgress) {\n            String desc = \"\";\n            switch (status) {\n                case FileDownloadStatus.pending:\n                    desc += \" pending\";\n                    builder.setProgress(getTotal(), getSofar(), true);\n                    break;\n                case FileDownloadStatus.started:\n                    desc += \" started\";\n                    builder.setProgress(getTotal(), getSofar(), true);\n                    break;\n                case FileDownloadStatus.progress:\n                    desc += \" progress\";\n                    builder.setProgress(getTotal(), getSofar(), getTotal() <= 0);\n                    break;\n                case FileDownloadStatus.retry:\n                    desc += \" retry\";\n                    builder.setProgress(getTotal(), getSofar(), true);\n                    break;\n                case FileDownloadStatus.error:\n                    desc += \" error\";\n                    builder.setProgress(getTotal(), getSofar(), false);\n                    break;\n                case FileDownloadStatus.paused:\n                    desc += \" paused\";\n                    builder.setProgress(getTotal(), getSofar(), false);\n                    break;\n                case FileDownloadStatus.completed:\n                    desc += \" completed\";\n                    builder.setProgress(getTotal(), getSofar(), false);\n                    break;\n                case FileDownloadStatus.warn:\n                    desc += \" warn\";\n                    builder.setProgress(0, 0, true);\n                    break;\n            }\n\n            builder.setContentTitle(getTitle()).setContentText(desc);\n            getManager().notify(getId(), builder.build());\n        }\n    }\n\n    @Override\n    protected void onDestroy() {\n        super.onDestroy();\n        notificationHelper.clear();\n        Utils.deleteNotificationChannel(channelId, getApplicationContext());\n    }\n\n    private CheckBox showNotificationCb;\n    private ProgressBar progressBar;\n    private View startDownload;\n\n    private void assignViews() {\n        showNotificationCb = findViewById(R.id.show_notification_cb);\n        progressBar = findViewById(R.id.progressBar);\n        startDownload = findViewById(R.id.view_start);\n    }\n}"
  },
  {
    "path": "demo/src/main/java/com/liulishuo/filedownloader/demo/SingleTaskTestActivity.java",
    "content": "package com.liulishuo.filedownloader.demo;\n\nimport android.os.Bundle;\nimport android.support.design.widget.Snackbar;\nimport android.support.v7.app.AppCompatActivity;\nimport android.view.View;\nimport android.widget.Button;\nimport android.widget.ProgressBar;\nimport android.widget.TextView;\n\nimport com.liulishuo.filedownloader.BaseDownloadTask;\nimport com.liulishuo.filedownloader.FileDownloadSampleListener;\nimport com.liulishuo.filedownloader.FileDownloader;\nimport com.liulishuo.filedownloader.util.FileDownloadUtils;\n\nimport java.io.File;\nimport java.lang.ref.WeakReference;\n\n/**\n * Created by Jacksgong on 12/21/15.\n */\npublic class SingleTaskTestActivity extends AppCompatActivity {\n\n    @Override\n    protected void onCreate(Bundle savedInstanceState) {\n        super.onCreate(savedInstanceState);\n        setContentView(R.layout.activity_single);\n\n        llsApkFilePath = FileDownloadUtils.getDefaultSaveRootPath() + File.separator + \"tmpdir1\" + File.separator +\n                Constant.LIULISHUO_CONTENT_DISPOSITION_FILENAME;\n        llsApkDir = FileDownloadUtils.getDefaultSaveRootPath() + File.separator + \"tmpdir1\";\n        normalTaskFilePath = FileDownloadUtils.getDefaultSaveRootPath() + File.separator + \"tmp2\";\n        chunkedFilePath = FileDownloadUtils.getDefaultSaveRootPath() + File.separator + \"chunked_data_tmp1\";\n\n        assignViews();\n\n        initFilePathEqualDirAndFileName();\n        initNormalDataAction();\n        initChunkTransferEncodingDataAction();\n    }\n\n\n    // test for the file path = dir path / content-disposition-filename\n    // task1: set {@code llsApkFilePath}\n    // task2: set {@code llsApkDir}\n    private void initFilePathEqualDirAndFileName() {\n        // task 1\n        startBtn1.setOnClickListener(new View.OnClickListener() {\n            @Override\n            public void onClick(View v) {\n                downloadId1 = createDownloadTask(1).start();\n            }\n        });\n\n        pauseBtn1.setOnClickListener(new View.OnClickListener() {\n            @Override\n            public void onClick(View v) {\n                FileDownloader.getImpl().pause(downloadId1);\n            }\n        });\n\n        deleteBtn1.setOnClickListener(new View.OnClickListener() {\n            @Override\n            public void onClick(View v) {\n                new File(llsApkFilePath).delete();\n                new File(FileDownloadUtils.getTempPath(llsApkFilePath)).delete();\n            }\n        });\n\n        // task 2\n        startBtn2.setOnClickListener(new View.OnClickListener() {\n            @Override\n            public void onClick(View v) {\n                downloadId2 = createDownloadTask(2).start();\n            }\n        });\n\n        pauseBtn2.setOnClickListener(new View.OnClickListener() {\n            @Override\n            public void onClick(View v) {\n                FileDownloader.getImpl().pause(downloadId2);\n            }\n        });\n\n        deleteBtn2.setOnClickListener(new View.OnClickListener() {\n            @Override\n            public void onClick(View v) {\n                new File(llsApkDir).delete();\n            }\n        });\n    }\n\n    // test for normal task.\n    private void initNormalDataAction() {\n        // task 3\n        startBtn3.setOnClickListener(new View.OnClickListener() {\n            @Override\n            public void onClick(View v) {\n                downloadId3 = createDownloadTask(3).start();\n            }\n        });\n\n        pauseBtn3.setOnClickListener(new View.OnClickListener() {\n            @Override\n            public void onClick(View v) {\n                FileDownloader.getImpl().pause(downloadId3);\n            }\n        });\n\n        deleteBtn3.setOnClickListener(new View.OnClickListener() {\n            @Override\n            public void onClick(View v) {\n                new File(normalTaskFilePath).delete();\n                new File(FileDownloadUtils.getTempPath(normalTaskFilePath)).delete();\n            }\n        });\n    }\n\n    // test for chunked downloading.\n    private void initChunkTransferEncodingDataAction() {\n        // task 4\n        startBtn4.setOnClickListener(new View.OnClickListener() {\n            @Override\n            public void onClick(View v) {\n                downloadId4 = createDownloadTask(4).start();\n            }\n        });\n\n        pauseBtn4.setOnClickListener(new View.OnClickListener() {\n            @Override\n            public void onClick(View v) {\n                FileDownloader.getImpl().pause(downloadId4);\n            }\n        });\n\n        deleteBtn4.setOnClickListener(new View.OnClickListener() {\n            @Override\n            public void onClick(View v) {\n                new File(chunkedFilePath).delete();\n                new File(FileDownloadUtils.getTempPath(chunkedFilePath)).delete();\n            }\n        });\n    }\n\n    @Override\n    protected void onDestroy() {\n        super.onDestroy();\n        FileDownloader.getImpl().pause(downloadId1);\n        FileDownloader.getImpl().pause(downloadId2);\n    }\n\n    private BaseDownloadTask createDownloadTask(final int position) {\n        final ViewHolder tag;\n        final String url;\n        boolean isDir = false;\n        String path;\n\n        switch (position) {\n            case 1:\n                url = Constant.LIULISHUO_APK_URL;\n                tag = new ViewHolder(new WeakReference<>(this), progressBar1, null, speedTv1, 1);\n                path = llsApkFilePath;\n                tag.setFilenameTv(filenameTv1);\n                break;\n            case 2:\n                url = Constant.LIULISHUO_APK_URL;\n                tag = new ViewHolder(new WeakReference<>(this), progressBar2, null, speedTv2, 2);\n                path = llsApkDir;\n                isDir = true;\n                tag.setFilenameTv(filenameTv2);\n                break;\n            case 3:\n                url = Constant.BIG_FILE_URLS[2];\n                tag = new ViewHolder(new WeakReference<>(this), progressBar3, null, speedTv3, 3);\n                path = normalTaskFilePath;\n                break;\n            default:\n                url = Constant.CHUNKED_TRANSFER_ENCODING_DATA_URLS[0];\n                tag = new ViewHolder(new WeakReference<>(this), progressBar4, detailTv4, speedTv4, 4);\n                path = chunkedFilePath;\n                break;\n\n        }\n\n        return FileDownloader.getImpl().create(url)\n                .setPath(path, isDir)\n                .setCallbackProgressTimes(300)\n                .setMinIntervalUpdateSpeed(400)\n                .setTag(tag)\n                .setListener(new FileDownloadSampleListener() {\n\n                    @Override\n                    protected void pending(BaseDownloadTask task, int soFarBytes, int totalBytes) {\n                        super.pending(task, soFarBytes, totalBytes);\n                        ((ViewHolder) task.getTag()).updatePending(task);\n                    }\n\n                    @Override\n                    protected void progress(BaseDownloadTask task, int soFarBytes, int totalBytes) {\n                        super.progress(task, soFarBytes, totalBytes);\n                        ((ViewHolder) task.getTag()).updateProgress(soFarBytes, totalBytes,\n                                task.getSpeed());\n                    }\n\n                    @Override\n                    protected void error(BaseDownloadTask task, Throwable e) {\n                        super.error(task, e);\n                        ((ViewHolder) task.getTag()).updateError(e, task.getSpeed());\n                    }\n\n                    @Override\n                    protected void connected(BaseDownloadTask task, String etag, boolean isContinue, int soFarBytes, int totalBytes) {\n                        super.connected(task, etag, isContinue, soFarBytes, totalBytes);\n                        ((ViewHolder) task.getTag()).updateConnected(etag, task.getFilename());\n                    }\n\n                    @Override\n                    protected void paused(BaseDownloadTask task, int soFarBytes, int totalBytes) {\n                        super.paused(task, soFarBytes, totalBytes);\n                        ((ViewHolder) task.getTag()).updatePaused(task.getSpeed());\n                    }\n\n                    @Override\n                    protected void completed(BaseDownloadTask task) {\n                        super.completed(task);\n                        ((ViewHolder) task.getTag()).updateCompleted(task);\n                    }\n\n                    @Override\n                    protected void warn(BaseDownloadTask task) {\n                        super.warn(task);\n                        ((ViewHolder) task.getTag()).updateWarn();\n                    }\n                });\n    }\n\n    private static class ViewHolder {\n        private ProgressBar pb;\n        private TextView detailTv;\n        private TextView speedTv;\n        private int position;\n        private TextView filenameTv;\n\n        private WeakReference<SingleTaskTestActivity> weakReferenceContext;\n\n        public ViewHolder(WeakReference<SingleTaskTestActivity> weakReferenceContext,\n                          final ProgressBar pb, final TextView detailTv, final TextView speedTv,\n                          final int position) {\n            this.weakReferenceContext = weakReferenceContext;\n            this.pb = pb;\n            this.detailTv = detailTv;\n            this.position = position;\n            this.speedTv = speedTv;\n        }\n\n        public void setFilenameTv(TextView filenameTv) {\n            this.filenameTv = filenameTv;\n        }\n\n        private void updateSpeed(int speed) {\n            speedTv.setText(String.format(\"%dKB/s\", speed));\n        }\n\n        public void updateProgress(final int sofar, final int total, final int speed) {\n            if (total == -1) {\n                // chunked transfer encoding data\n                pb.setIndeterminate(true);\n            } else {\n                pb.setMax(total);\n                pb.setProgress(sofar);\n            }\n\n            updateSpeed(speed);\n\n            if (detailTv != null) {\n                detailTv.setText(String.format(\"sofar: %d total: %d\", sofar, total));\n            }\n        }\n\n        public void updatePending(BaseDownloadTask task) {\n            if (filenameTv != null) {\n                filenameTv.setText(task.getFilename());\n            }\n        }\n\n        public void updatePaused(final int speed) {\n            toast(String.format(\"paused %d\", position));\n            updateSpeed(speed);\n            pb.setIndeterminate(false);\n        }\n\n        public void updateConnected(String etag, String filename) {\n            if (filenameTv != null) {\n                filenameTv.setText(filename);\n            }\n        }\n\n        public void updateWarn() {\n            toast(String.format(\"warn %d\", position));\n            pb.setIndeterminate(false);\n        }\n\n        public void updateError(final Throwable ex, final int speed) {\n            toast(String.format(\"error %d %s\", position, ex));\n            updateSpeed(speed);\n            pb.setIndeterminate(false);\n            ex.printStackTrace();\n        }\n\n        public void updateCompleted(final BaseDownloadTask task) {\n\n            toast(String.format(\"completed %d %s\", position, task.getTargetFilePath()));\n\n            if (detailTv != null) {\n                detailTv.setText(String.format(\"sofar: %d total: %d\",\n                        task.getSmallFileSoFarBytes(), task.getSmallFileTotalBytes()));\n            }\n\n            updateSpeed(task.getSpeed());\n            pb.setIndeterminate(false);\n            pb.setMax(task.getSmallFileTotalBytes());\n            pb.setProgress(task.getSmallFileSoFarBytes());\n        }\n\n        private void toast(final String msg) {\n            if (this.weakReferenceContext != null && this.weakReferenceContext.get() != null) {\n                Snackbar.make(weakReferenceContext.get().startBtn1, msg, Snackbar.LENGTH_LONG).show();\n            }\n        }\n\n    }\n\n    private int downloadId1;\n    private int downloadId2;\n    private int downloadId3;\n    private int downloadId4;\n\n\n    private String llsApkFilePath;\n    private String llsApkDir;\n    private String normalTaskFilePath;\n    private String chunkedFilePath;\n\n    private Button startBtn1;\n    private Button pauseBtn1;\n    private Button deleteBtn1;\n    private TextView filenameTv1;\n    private TextView speedTv1;\n    private ProgressBar progressBar1;\n    private Button startBtn2;\n    private Button pauseBtn2;\n    private Button deleteBtn2;\n    private TextView filenameTv2;\n    private TextView speedTv2;\n    private ProgressBar progressBar2;\n    private Button startBtn3;\n    private Button pauseBtn3;\n    private Button deleteBtn3;\n    private TextView speedTv3;\n    private ProgressBar progressBar3;\n    private Button startBtn4;\n    private Button pauseBtn4;\n    private Button deleteBtn4;\n    private TextView detailTv4;\n    private TextView speedTv4;\n    private ProgressBar progressBar4;\n\n    private void assignViews() {\n        startBtn1 = (Button) findViewById(R.id.start_btn_1);\n        pauseBtn1 = (Button) findViewById(R.id.pause_btn_1);\n        deleteBtn1 = (Button) findViewById(R.id.delete_btn_1);\n        filenameTv1 = (TextView) findViewById(R.id.filename_tv_1);\n        speedTv1 = (TextView) findViewById(R.id.speed_tv_1);\n        progressBar1 = (ProgressBar) findViewById(R.id.progressBar_1);\n        startBtn2 = (Button) findViewById(R.id.start_btn_2);\n        pauseBtn2 = (Button) findViewById(R.id.pause_btn_2);\n        deleteBtn2 = (Button) findViewById(R.id.delete_btn_2);\n        filenameTv2 = (TextView) findViewById(R.id.filename_tv_2);\n        speedTv2 = (TextView) findViewById(R.id.speed_tv_2);\n        progressBar2 = (ProgressBar) findViewById(R.id.progressBar_2);\n        startBtn3 = (Button) findViewById(R.id.start_btn_3);\n        pauseBtn3 = (Button) findViewById(R.id.pause_btn_3);\n        deleteBtn3 = (Button) findViewById(R.id.delete_btn_3);\n        speedTv3 = (TextView) findViewById(R.id.speed_tv_3);\n        progressBar3 = (ProgressBar) findViewById(R.id.progressBar_3);\n        startBtn4 = (Button) findViewById(R.id.start_btn_4);\n        pauseBtn4 = (Button) findViewById(R.id.pause_btn_4);\n        deleteBtn4 = (Button) findViewById(R.id.delete_btn_4);\n        detailTv4 = (TextView) findViewById(R.id.detail_tv_4);\n        speedTv4 = (TextView) findViewById(R.id.speed_tv_4);\n        progressBar4 = (ProgressBar) findViewById(R.id.progressBar_4);\n    }\n\n\n}\n"
  },
  {
    "path": "demo/src/main/java/com/liulishuo/filedownloader/demo/TasksManagerDemoActivity.java",
    "content": "/*\n * Copyright (c) 2015 LingoChamp Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *    http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.liulishuo.filedownloader.demo;\n\nimport android.content.ContentValues;\nimport android.content.Context;\nimport android.database.Cursor;\nimport android.database.sqlite.SQLiteDatabase;\nimport android.database.sqlite.SQLiteOpenHelper;\nimport android.os.Bundle;\nimport android.support.v7.app.AppCompatActivity;\nimport android.support.v7.widget.LinearLayoutManager;\nimport android.support.v7.widget.RecyclerView;\nimport android.text.TextUtils;\nimport android.util.SparseArray;\nimport android.view.LayoutInflater;\nimport android.view.View;\nimport android.view.ViewGroup;\nimport android.widget.Button;\nimport android.widget.ProgressBar;\nimport android.widget.TextView;\n\nimport com.liulishuo.filedownloader.BaseDownloadTask;\nimport com.liulishuo.filedownloader.FileDownloadConnectListener;\nimport com.liulishuo.filedownloader.FileDownloadListener;\nimport com.liulishuo.filedownloader.FileDownloadSampleListener;\nimport com.liulishuo.filedownloader.FileDownloader;\nimport com.liulishuo.filedownloader.model.FileDownloadStatus;\nimport com.liulishuo.filedownloader.util.FileDownloadUtils;\n\nimport java.io.File;\nimport java.lang.ref.WeakReference;\nimport java.util.ArrayList;\nimport java.util.List;\n\n/**\n * Created by Jacksgong on 1/9/16.\n */\npublic class TasksManagerDemoActivity extends AppCompatActivity {\n\n    private TaskItemAdapter adapter;\n\n    @Override\n    protected void onCreate(Bundle savedInstanceState) {\n        super.onCreate(savedInstanceState);\n        setContentView(R.layout.activity_tasks_manager_demo);\n\n        final RecyclerView recyclerView = (RecyclerView) findViewById(R.id.recycler_view);\n\n        recyclerView.setLayoutManager(new LinearLayoutManager(this));\n        recyclerView.setAdapter(adapter = new TaskItemAdapter());\n\n\n        TasksManager.getImpl().onCreate(new WeakReference<>(this));\n    }\n\n    public void postNotifyDataChanged() {\n        if (adapter != null) {\n            runOnUiThread(new Runnable() {\n                @Override\n                public void run() {\n                    if (adapter != null) {\n                        adapter.notifyDataSetChanged();\n                    }\n                }\n            });\n        }\n    }\n\n    @Override\n    protected void onDestroy() {\n        TasksManager.getImpl().onDestroy();\n        adapter = null;\n        FileDownloader.getImpl().pauseAll();\n        super.onDestroy();\n    }\n\n\n    // ============================================================================ view adapter ===\n\n    private static class TaskItemViewHolder extends RecyclerView.ViewHolder {\n        public TaskItemViewHolder(View itemView) {\n            super(itemView);\n            assignViews();\n        }\n\n        private View findViewById(final int id) {\n            return itemView.findViewById(id);\n        }\n\n        /**\n         * viewHolder position\n         */\n        private int position;\n        /**\n         * download id\n         */\n        private int id;\n\n        public void update(final int id, final int position) {\n            this.id = id;\n            this.position = position;\n        }\n\n\n        public void updateDownloaded() {\n            taskPb.setMax(1);\n            taskPb.setProgress(1);\n\n            taskStatusTv.setText(R.string.tasks_manager_demo_status_completed);\n            taskActionBtn.setText(R.string.delete);\n        }\n\n        public void updateNotDownloaded(final int status, final long sofar, final long total) {\n            if (sofar > 0 && total > 0) {\n                final float percent = sofar\n                        / (float) total;\n                taskPb.setMax(100);\n                taskPb.setProgress((int) (percent * 100));\n            } else {\n                taskPb.setMax(1);\n                taskPb.setProgress(0);\n            }\n\n            switch (status) {\n                case FileDownloadStatus.error:\n                    taskStatusTv.setText(R.string.tasks_manager_demo_status_error);\n                    break;\n                case FileDownloadStatus.paused:\n                    taskStatusTv.setText(R.string.tasks_manager_demo_status_paused);\n                    break;\n                default:\n                    taskStatusTv.setText(R.string.tasks_manager_demo_status_not_downloaded);\n                    break;\n            }\n            taskActionBtn.setText(R.string.start);\n        }\n\n        public void updateDownloading(final int status, final long sofar, final long total) {\n            final float percent = sofar\n                    / (float) total;\n            taskPb.setMax(100);\n            taskPb.setProgress((int) (percent * 100));\n\n            switch (status) {\n                case FileDownloadStatus.pending:\n                    taskStatusTv.setText(R.string.tasks_manager_demo_status_pending);\n                    break;\n                case FileDownloadStatus.started:\n                    taskStatusTv.setText(R.string.tasks_manager_demo_status_started);\n                    break;\n                case FileDownloadStatus.connected:\n                    taskStatusTv.setText(R.string.tasks_manager_demo_status_connected);\n                    break;\n                case FileDownloadStatus.progress:\n                    taskStatusTv.setText(R.string.tasks_manager_demo_status_progress);\n                    break;\n                default:\n                    taskStatusTv.setText(DemoApplication.CONTEXT.getString(\n                            R.string.tasks_manager_demo_status_downloading, status));\n                    break;\n            }\n\n            taskActionBtn.setText(R.string.pause);\n        }\n\n        private TextView taskNameTv;\n        private TextView taskStatusTv;\n        private ProgressBar taskPb;\n        private Button taskActionBtn;\n\n        private void assignViews() {\n            taskNameTv = (TextView) findViewById(R.id.task_name_tv);\n            taskStatusTv = (TextView) findViewById(R.id.task_status_tv);\n            taskPb = (ProgressBar) findViewById(R.id.task_pb);\n            taskActionBtn = (Button) findViewById(R.id.task_action_btn);\n        }\n\n    }\n\n    private static class TaskItemAdapter extends RecyclerView.Adapter<TaskItemViewHolder> {\n\n        private FileDownloadListener taskDownloadListener = new FileDownloadSampleListener() {\n\n            private TaskItemViewHolder checkCurrentHolder(final BaseDownloadTask task) {\n                final TaskItemViewHolder tag = (TaskItemViewHolder) task.getTag();\n                if (tag.id != task.getId()) {\n                    return null;\n                }\n\n                return tag;\n            }\n\n            @Override\n            protected void pending(BaseDownloadTask task, int soFarBytes, int totalBytes) {\n                super.pending(task, soFarBytes, totalBytes);\n                final TaskItemViewHolder tag = checkCurrentHolder(task);\n                if (tag == null) {\n                    return;\n                }\n\n                tag.updateDownloading(FileDownloadStatus.pending, soFarBytes\n                        , totalBytes);\n                tag.taskStatusTv.setText(R.string.tasks_manager_demo_status_pending);\n            }\n\n            @Override\n            protected void started(BaseDownloadTask task) {\n                super.started(task);\n                final TaskItemViewHolder tag = checkCurrentHolder(task);\n                if (tag == null) {\n                    return;\n                }\n\n                tag.taskStatusTv.setText(R.string.tasks_manager_demo_status_started);\n            }\n\n            @Override\n            protected void connected(BaseDownloadTask task, String etag, boolean isContinue, int soFarBytes, int totalBytes) {\n                super.connected(task, etag, isContinue, soFarBytes, totalBytes);\n                final TaskItemViewHolder tag = checkCurrentHolder(task);\n                if (tag == null) {\n                    return;\n                }\n\n                tag.updateDownloading(FileDownloadStatus.connected, soFarBytes\n                        , totalBytes);\n                tag.taskStatusTv.setText(R.string.tasks_manager_demo_status_connected);\n            }\n\n            @Override\n            protected void progress(BaseDownloadTask task, int soFarBytes, int totalBytes) {\n                super.progress(task, soFarBytes, totalBytes);\n                final TaskItemViewHolder tag = checkCurrentHolder(task);\n                if (tag == null) {\n                    return;\n                }\n\n                tag.updateDownloading(FileDownloadStatus.progress, soFarBytes\n                        , totalBytes);\n            }\n\n            @Override\n            protected void error(BaseDownloadTask task, Throwable e) {\n                super.error(task, e);\n                final TaskItemViewHolder tag = checkCurrentHolder(task);\n                if (tag == null) {\n                    return;\n                }\n\n                tag.updateNotDownloaded(FileDownloadStatus.error, task.getLargeFileSoFarBytes()\n                        , task.getLargeFileTotalBytes());\n                TasksManager.getImpl().removeTaskForViewHolder(task.getId());\n            }\n\n            @Override\n            protected void paused(BaseDownloadTask task, int soFarBytes, int totalBytes) {\n                super.paused(task, soFarBytes, totalBytes);\n                final TaskItemViewHolder tag = checkCurrentHolder(task);\n                if (tag == null) {\n                    return;\n                }\n\n                tag.updateNotDownloaded(FileDownloadStatus.paused, soFarBytes, totalBytes);\n                tag.taskStatusTv.setText(R.string.tasks_manager_demo_status_paused);\n                TasksManager.getImpl().removeTaskForViewHolder(task.getId());\n            }\n\n            @Override\n            protected void completed(BaseDownloadTask task) {\n                super.completed(task);\n                final TaskItemViewHolder tag = checkCurrentHolder(task);\n                if (tag == null) {\n                    return;\n                }\n\n                tag.updateDownloaded();\n                TasksManager.getImpl().removeTaskForViewHolder(task.getId());\n            }\n        };\n        private View.OnClickListener taskActionOnClickListener = new View.OnClickListener() {\n            @Override\n            public void onClick(View v) {\n                if (v.getTag() == null) {\n                    return;\n                }\n\n                TaskItemViewHolder holder = (TaskItemViewHolder) v.getTag();\n\n                CharSequence action = ((TextView) v).getText();\n                if (action.equals(v.getResources().getString(R.string.pause))) {\n                    // to pause\n                    FileDownloader.getImpl().pause(holder.id);\n                } else if (action.equals(v.getResources().getString(R.string.start))) {\n                    // to start\n                    // to start\n                    final TasksManagerModel model = TasksManager.getImpl().get(holder.position);\n                    final BaseDownloadTask task = FileDownloader.getImpl().create(model.getUrl())\n                            .setPath(model.getPath())\n                            .setCallbackProgressTimes(100)\n                            .setListener(taskDownloadListener);\n\n                    TasksManager.getImpl()\n                            .addTaskForViewHolder(task);\n\n                    TasksManager.getImpl()\n                            .updateViewHolder(holder.id, holder);\n\n                    task.start();\n                } else if (action.equals(v.getResources().getString(R.string.delete))) {\n                    // to delete\n                    new File(TasksManager.getImpl().get(holder.position).getPath()).delete();\n                    holder.taskActionBtn.setEnabled(true);\n                    holder.updateNotDownloaded(FileDownloadStatus.INVALID_STATUS, 0, 0);\n                }\n            }\n        };\n\n        @Override\n        public TaskItemViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {\n            TaskItemViewHolder holder = new TaskItemViewHolder(\n                    LayoutInflater.from(\n                            parent.getContext())\n                            .inflate(R.layout.item_tasks_manager, parent, false));\n\n            holder.taskActionBtn.setOnClickListener(taskActionOnClickListener);\n            return holder;\n        }\n\n        @Override\n        public void onBindViewHolder(TaskItemViewHolder holder, int position) {\n            final TasksManagerModel model = TasksManager.getImpl().get(position);\n\n            holder.update(model.getId(), position);\n            holder.taskActionBtn.setTag(holder);\n            holder.taskNameTv.setText(model.getName());\n\n            TasksManager.getImpl()\n                    .updateViewHolder(holder.id, holder);\n\n            holder.taskActionBtn.setEnabled(true);\n\n\n            if (TasksManager.getImpl().isReady()) {\n                final int status = TasksManager.getImpl().getStatus(model.getId(), model.getPath());\n                if (status == FileDownloadStatus.pending || status == FileDownloadStatus.started ||\n                        status == FileDownloadStatus.connected) {\n                    // start task, but file not created yet\n                    holder.updateDownloading(status, TasksManager.getImpl().getSoFar(model.getId())\n                            , TasksManager.getImpl().getTotal(model.getId()));\n                } else if (!new File(model.getPath()).exists() &&\n                        !new File(FileDownloadUtils.getTempPath(model.getPath())).exists()) {\n                    // not exist file\n                    holder.updateNotDownloaded(status, 0, 0);\n                } else if (TasksManager.getImpl().isDownloaded(status)) {\n                    // already downloaded and exist\n                    holder.updateDownloaded();\n                } else if (status == FileDownloadStatus.progress) {\n                    // downloading\n                    holder.updateDownloading(status, TasksManager.getImpl().getSoFar(model.getId())\n                            , TasksManager.getImpl().getTotal(model.getId()));\n                } else {\n                    // not start\n                    holder.updateNotDownloaded(status, TasksManager.getImpl().getSoFar(model.getId())\n                            , TasksManager.getImpl().getTotal(model.getId()));\n                }\n            } else {\n                holder.taskStatusTv.setText(R.string.tasks_manager_demo_status_loading);\n                holder.taskActionBtn.setEnabled(false);\n            }\n        }\n\n        @Override\n        public int getItemCount() {\n            return TasksManager.getImpl().getTaskCounts();\n        }\n    }\n\n\n    // ============================================================================ controller ====\n\n    public static class TasksManager {\n        private final static class HolderClass {\n            private final static TasksManager INSTANCE\n                    = new TasksManagerDemoActivity.TasksManager();\n        }\n\n        public static TasksManager getImpl() {\n            return HolderClass.INSTANCE;\n        }\n\n        private TasksManagerDBController dbController;\n        private List<TasksManagerModel> modelList;\n\n        private TasksManager() {\n            dbController = new TasksManagerDBController();\n            modelList = dbController.getAllTasks();\n\n            initDemo();\n        }\n\n        private void initDemo() {\n            if (modelList.size() <= 0) {\n                final int demoSize = Constant.BIG_FILE_URLS.length;\n                for (int i = 0; i < demoSize; i++) {\n                    final String url = Constant.BIG_FILE_URLS[i];\n                    addTask(url);\n                }\n            }\n        }\n\n        private SparseArray<BaseDownloadTask> taskSparseArray = new SparseArray<>();\n\n        public void addTaskForViewHolder(final BaseDownloadTask task) {\n            taskSparseArray.put(task.getId(), task);\n        }\n\n        public void removeTaskForViewHolder(final int id) {\n            taskSparseArray.remove(id);\n        }\n\n        public void updateViewHolder(final int id, final TaskItemViewHolder holder) {\n            final BaseDownloadTask task = taskSparseArray.get(id);\n            if (task == null) {\n                return;\n            }\n\n            task.setTag(holder);\n        }\n\n        public void releaseTask() {\n            taskSparseArray.clear();\n        }\n\n        private FileDownloadConnectListener listener;\n\n        private void registerServiceConnectionListener(final WeakReference<TasksManagerDemoActivity>\n                                                               activityWeakReference) {\n            if (listener != null) {\n                FileDownloader.getImpl().removeServiceConnectListener(listener);\n            }\n\n            listener = new FileDownloadConnectListener() {\n\n                @Override\n                public void connected() {\n                    if (activityWeakReference == null\n                            || activityWeakReference.get() == null) {\n                        return;\n                    }\n\n                    activityWeakReference.get().postNotifyDataChanged();\n                }\n\n                @Override\n                public void disconnected() {\n                    if (activityWeakReference == null\n                            || activityWeakReference.get() == null) {\n                        return;\n                    }\n\n                    activityWeakReference.get().postNotifyDataChanged();\n                }\n            };\n\n            FileDownloader.getImpl().addServiceConnectListener(listener);\n        }\n\n        private void unregisterServiceConnectionListener() {\n            FileDownloader.getImpl().removeServiceConnectListener(listener);\n            listener = null;\n        }\n\n        public void onCreate(final WeakReference<TasksManagerDemoActivity> activityWeakReference) {\n            if (!FileDownloader.getImpl().isServiceConnected()) {\n                FileDownloader.getImpl().bindService();\n                registerServiceConnectionListener(activityWeakReference);\n            }\n        }\n\n        public void onDestroy() {\n            unregisterServiceConnectionListener();\n            releaseTask();\n        }\n\n        public boolean isReady() {\n            return FileDownloader.getImpl().isServiceConnected();\n        }\n\n        public TasksManagerModel get(final int position) {\n            return modelList.get(position);\n        }\n\n        public TasksManagerModel getById(final int id) {\n            for (TasksManagerModel model : modelList) {\n                if (model.getId() == id) {\n                    return model;\n                }\n            }\n\n            return null;\n        }\n\n        /**\n         * @param status Download Status\n         * @return has already downloaded\n         * @see FileDownloadStatus\n         */\n        public boolean isDownloaded(final int status) {\n            return status == FileDownloadStatus.completed;\n        }\n\n        public int getStatus(final int id, String path) {\n            return FileDownloader.getImpl().getStatus(id, path);\n        }\n\n        public long getTotal(final int id) {\n            return FileDownloader.getImpl().getTotal(id);\n        }\n\n        public long getSoFar(final int id) {\n            return FileDownloader.getImpl().getSoFar(id);\n        }\n\n        public int getTaskCounts() {\n            return modelList.size();\n        }\n\n        public TasksManagerModel addTask(final String url) {\n            return addTask(url, createPath(url));\n        }\n\n        public TasksManagerModel addTask(final String url, final String path) {\n            if (TextUtils.isEmpty(url) || TextUtils.isEmpty(path)) {\n                return null;\n            }\n\n            final int id = FileDownloadUtils.generateId(url, path);\n            TasksManagerModel model = getById(id);\n            if (model != null) {\n                return model;\n            }\n            final TasksManagerModel newModel = dbController.addTask(url, path);\n            if (newModel != null) {\n                modelList.add(newModel);\n            }\n\n            return newModel;\n        }\n\n        public String createPath(final String url) {\n            if (TextUtils.isEmpty(url)) {\n                return null;\n            }\n\n            return FileDownloadUtils.getDefaultSaveFilePath(url);\n        }\n    }\n\n    private static class TasksManagerDBController {\n        public final static String TABLE_NAME = \"tasksmanger\";\n        private final SQLiteDatabase db;\n\n        private TasksManagerDBController() {\n            TasksManagerDBOpenHelper openHelper = new TasksManagerDBOpenHelper(DemoApplication.CONTEXT);\n\n            db = openHelper.getWritableDatabase();\n        }\n\n        public List<TasksManagerModel> getAllTasks() {\n            final Cursor c = db.rawQuery(\"SELECT * FROM \" + TABLE_NAME, null);\n\n            final List<TasksManagerModel> list = new ArrayList<>();\n            try {\n                if (!c.moveToLast()) {\n                    return list;\n                }\n\n                do {\n                    TasksManagerModel model = new TasksManagerModel();\n                    model.setId(c.getInt(c.getColumnIndex(TasksManagerModel.ID)));\n                    model.setName(c.getString(c.getColumnIndex(TasksManagerModel.NAME)));\n                    model.setUrl(c.getString(c.getColumnIndex(TasksManagerModel.URL)));\n                    model.setPath(c.getString(c.getColumnIndex(TasksManagerModel.PATH)));\n                    list.add(model);\n                } while (c.moveToPrevious());\n            } finally {\n                if (c != null) {\n                    c.close();\n                }\n            }\n\n            return list;\n        }\n\n        public TasksManagerModel addTask(final String url, final String path) {\n            if (TextUtils.isEmpty(url) || TextUtils.isEmpty(path)) {\n                return null;\n            }\n\n            // have to use FileDownloadUtils.generateId to associate TasksManagerModel with FileDownloader\n            final int id = FileDownloadUtils.generateId(url, path);\n\n            TasksManagerModel model = new TasksManagerModel();\n            model.setId(id);\n            model.setName(DemoApplication.CONTEXT.getString(R.string.tasks_manager_demo_name, id));\n            model.setUrl(url);\n            model.setPath(path);\n\n            final boolean succeed = db.insert(TABLE_NAME, null, model.toContentValues()) != -1;\n            return succeed ? model : null;\n        }\n\n\n    }\n\n    // ----------------------- model\n    private static class TasksManagerDBOpenHelper extends SQLiteOpenHelper {\n        public final static String DATABASE_NAME = \"tasksmanager.db\";\n        public final static int DATABASE_VERSION = 2;\n\n        public TasksManagerDBOpenHelper(Context context) {\n            super(context, DATABASE_NAME, null, DATABASE_VERSION);\n        }\n\n\n        @Override\n        public void onCreate(SQLiteDatabase db) {\n            db.execSQL(\"CREATE TABLE IF NOT EXISTS \"\n                    + TasksManagerDBController.TABLE_NAME\n                    + String.format(\n                    \"(\"\n                            + \"%s INTEGER PRIMARY KEY, \" // id, download id\n                            + \"%s VARCHAR, \" // name\n                            + \"%s VARCHAR, \" // url\n                            + \"%s VARCHAR \" // path\n                            + \")\"\n                    , TasksManagerModel.ID\n                    , TasksManagerModel.NAME\n                    , TasksManagerModel.URL\n                    , TasksManagerModel.PATH\n\n            ));\n        }\n\n        @Override\n        public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {\n            if (oldVersion == 1 && newVersion == 2) {\n                db.delete(TasksManagerDBController.TABLE_NAME, null, null);\n            }\n        }\n    }\n\n    private static class TasksManagerModel {\n        public final static String ID = \"id\";\n        public final static String NAME = \"name\";\n        public final static String URL = \"url\";\n        public final static String PATH = \"path\";\n\n        private int id;\n        private String name;\n        private String url;\n        private String path;\n\n        public int getId() {\n            return id;\n        }\n\n        public void setId(int id) {\n            this.id = id;\n        }\n\n        public String getName() {\n            return name;\n        }\n\n        public void setName(String name) {\n            this.name = name;\n        }\n\n        public String getUrl() {\n            return url;\n        }\n\n        public void setUrl(String url) {\n            this.url = url;\n        }\n\n        public String getPath() {\n            return path;\n        }\n\n        public void setPath(String path) {\n            this.path = path;\n        }\n\n        public ContentValues toContentValues() {\n            ContentValues cv = new ContentValues();\n            cv.put(ID, id);\n            cv.put(NAME, name);\n            cv.put(URL, url);\n            cv.put(PATH, path);\n            return cv;\n        }\n    }\n\n\n}\n"
  },
  {
    "path": "demo/src/main/java/com/liulishuo/filedownloader/demo/Utils.java",
    "content": "/*\n * Copyright (c) 2015 LingoChamp Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *   http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.liulishuo.filedownloader.demo;\n\nimport android.app.NotificationChannel;\nimport android.app.NotificationManager;\nimport android.content.Context;\nimport android.os.Build;\nimport android.support.annotation.Nullable;\n\npublic class Utils {\n    public static void createNotificationChannel(\n            String channelId,\n            String channelName,\n            Context context\n    ) {\n        NotificationManager notificationManager = getNotificationManager(context);\n        if (notificationManager == null) return;\n        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {\n            NotificationChannel channel = new NotificationChannel(\n                    channelId,\n                    channelName,\n                    NotificationManager.IMPORTANCE_LOW);\n            notificationManager.createNotificationChannel(channel);\n        }\n    }\n\n    public static void deleteNotificationChannel(String channelId, Context context) {\n        NotificationManager notificationManager = getNotificationManager(context);\n        if (notificationManager == null) return;\n        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {\n            notificationManager.deleteNotificationChannel(channelId);\n        }\n    }\n\n    @Nullable\n    public static NotificationManager getNotificationManager(Context context) {\n        return ((NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE));\n    }\n}\n"
  },
  {
    "path": "demo/src/main/java/com/liulishuo/filedownloader/demo/performance/IntParcel.java",
    "content": "/*\n * Copyright (c) 2015 LingoChamp Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *    http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.liulishuo.filedownloader.demo.performance;\n\nimport android.os.Parcel;\nimport android.os.Parcelable;\n\n/**\n * Created by Jacksgong on 1/4/16.\n */\npublic class IntParcel implements Parcelable {\n\n    private int v1 = Integer.MAX_VALUE;\n    private int v2 = Integer.MAX_VALUE;\n    private int v3 = Integer.MAX_VALUE;\n\n\n    public void operate() {\n        v1 -= 11;\n        v2 -= 12;\n        v3 -= 13;\n    }\n\n    @Override\n    public int describeContents() {\n        return 0;\n    }\n\n    @Override\n    public void writeToParcel(Parcel dest, int flags) {\n        dest.writeInt(this.v1);\n        dest.writeInt(this.v2);\n        dest.writeInt(this.v3);\n    }\n\n    public IntParcel() {\n    }\n\n    protected IntParcel(Parcel in) {\n        this.v1 = in.readInt();\n        this.v2 = in.readInt();\n        this.v3 = in.readInt();\n    }\n\n    public static final Creator<IntParcel> CREATOR = new Creator<IntParcel>() {\n        public IntParcel createFromParcel(Parcel source) {\n            return new IntParcel(source);\n        }\n\n        public IntParcel[] newArray(int size) {\n            return new IntParcel[size];\n        }\n    };\n}\n"
  },
  {
    "path": "demo/src/main/java/com/liulishuo/filedownloader/demo/performance/LongParcel.java",
    "content": "/*\n * Copyright (c) 2015 LingoChamp Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *    http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.liulishuo.filedownloader.demo.performance;\n\nimport android.os.Parcel;\nimport android.os.Parcelable;\n\n/**\n * Created by Jacksgong on 1/4/16.\n */\npublic class LongParcel implements Parcelable {\n\n    private long v1 = Integer.MAX_VALUE;\n    private long v2 = Integer.MAX_VALUE;\n    private long v3 = Integer.MAX_VALUE;\n\n    public void operate() {\n        v1 -= 11;\n        v2 -= 12;\n        v3 -= 13;\n    }\n\n    @Override\n    public int describeContents() {\n        return 0;\n    }\n\n    @Override\n    public void writeToParcel(Parcel dest, int flags) {\n        dest.writeLong(this.v1);\n        dest.writeLong(this.v2);\n        dest.writeLong(this.v3);\n    }\n\n    public LongParcel() {\n    }\n\n    protected LongParcel(Parcel in) {\n        this.v1 = in.readLong();\n        this.v2 = in.readLong();\n        this.v3 = in.readLong();\n    }\n\n    public static final Creator<LongParcel> CREATOR = new Creator<LongParcel>() {\n        public LongParcel createFromParcel(Parcel source) {\n            return new LongParcel(source);\n        }\n\n        public LongParcel[] newArray(int size) {\n            return new LongParcel[size];\n        }\n    };\n}\n"
  },
  {
    "path": "demo/src/main/java/com/liulishuo/filedownloader/demo/performance/PerformanceTestActivity.java",
    "content": "/*\n * Copyright (c) 2015 LingoChamp Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *    http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.liulishuo.filedownloader.demo.performance;\n\nimport android.os.Bundle;\nimport android.os.Parcel;\nimport android.support.v7.app.AppCompatActivity;\nimport android.support.v7.widget.AppCompatSeekBar;\nimport android.view.View;\nimport android.widget.ScrollView;\nimport android.widget.TextView;\n\nimport com.liulishuo.filedownloader.demo.R;\nimport com.liulishuo.filedownloader.util.FileDownloadUtils;\n\nimport java.io.BufferedOutputStream;\nimport java.io.File;\nimport java.io.FileNotFoundException;\nimport java.io.FileOutputStream;\nimport java.io.IOException;\nimport java.io.InputStream;\nimport java.io.RandomAccessFile;\nimport java.nio.ByteBuffer;\n\nimport okio.Buffer;\nimport okio.Okio;\nimport okio.Sink;\nimport okio.Source;\n\n/**\n * Created by Jacksgong on 1/4/16.\n */\npublic class PerformanceTestActivity extends AppCompatActivity {\n\n    private final static int TIMES = 100000;\n\n    @Override\n    protected void onCreate(Bundle savedInstanceState) {\n        super.onCreate(savedInstanceState);\n        setContentView(R.layout.activity_performance);\n        assignViews();\n    }\n\n    public void onClickLongOperate(final View view) {\n        final long start = System.currentTimeMillis();\n\n        final LongParcel longParcel = new LongParcel();\n        for (int i = 0; i < TIMES; i++) {\n            longParcel.operate();\n        }\n\n        infoAppend(\"Long Operate\", start);\n    }\n\n    public void onClickLongParcel(final View view) {\n        final long start = System.currentTimeMillis();\n\n        final LongParcel longParcel = new LongParcel();\n        for (int i = 0; i < TIMES; i++) {\n            Parcel p = Parcel.obtain();\n            longParcel.writeToParcel(p, 0);\n            LongParcel longParcelCopy = new LongParcel(p);\n        }\n\n        infoAppend(\"Long Parcel and Alloc [and GC]\", start);\n    }\n\n    public void onClickIntOperate(final View view) {\n        final long start = System.currentTimeMillis();\n\n        final IntParcel intParcel = new IntParcel();\n        for (int i = 0; i < TIMES; i++) {\n            intParcel.operate();\n        }\n\n        infoAppend(\"Int Operate\", start);\n    }\n\n    public void onClickIntParcel(final View view) {\n        final long start = System.currentTimeMillis();\n\n        final IntParcel intParcel = new IntParcel();\n        for (int i = 0; i < TIMES; i++) {\n            Parcel p = Parcel.obtain();\n            intParcel.writeToParcel(p, 0);\n            IntParcel intParcelCopy = new IntParcel(p);\n        }\n\n        infoAppend(\"Int Parcel and Alloc [and GC]\", start);\n    }\n\n    private static final int BUFFER_SIZE = 1024 * 4;\n    private String writePerformanceTestPath = FileDownloadUtils.getDefaultSaveRootPath()\n            + File.separator + \"performance\";\n\n    private static final int TENTH_MILLI_TO_NANO = 100000;\n\n    public void onClickWriteTest(final View view) {\n        FileOutputStream fos = null;\n        InputStream inputStream = initPerformanceTest();\n        byte[] buff = new byte[BUFFER_SIZE];\n        long start = System.currentTimeMillis();\n\n\n        int tenthMilliSec = ioPerformanceSb.getProgress();\n        int sleepMilliSec = tenthMilliSec / 10;\n        int sleepNanoSec = (tenthMilliSec - (tenthMilliSec / 10) * 10) * TENTH_MILLI_TO_NANO;\n\n        infoTv.append(String.format(\"Output test with %.1f ms extra operate\\n\",\n                tenthMilliSec / 10.0f));\n\n        // ---------------------- FileOutputStream\n        try {\n            fos = new FileOutputStream(writePerformanceTestPath, true);\n            do {\n                int byteCount = inputStream.read(buff);\n                if (byteCount == -1) {\n                    break;\n                }\n                fos.write(buff, 0, byteCount);\n\n                if (sleepMilliSec > 0 || sleepNanoSec > 0) {\n                    try {\n                        Thread.sleep(sleepMilliSec, sleepNanoSec);\n                    } catch (InterruptedException e) {\n                        e.printStackTrace();\n                    }\n                }\n\n            } while (true);\n        } catch (FileNotFoundException e) {\n            e.printStackTrace();\n        } catch (IOException e) {\n            e.printStackTrace();\n        } finally {\n            if (fos != null) {\n                try {\n                    fos.getFD().sync();\n                    fos.close();\n                } catch (IOException e) {\n                    e.printStackTrace();\n                }\n            }\n\n            if (inputStream != null) {\n                try {\n                    inputStream.close();\n                } catch (IOException e) {\n                    e.printStackTrace();\n                }\n            }\n        }\n\n        infoAppend(\"FileOutputStream\", start);\n\n        BufferedOutputStream bos = null;\n        inputStream = initPerformanceTest();\n        start = System.currentTimeMillis();\n\n        // ---------------------- BufferedOutputStream\n        try {\n            bos = new BufferedOutputStream(new FileOutputStream(writePerformanceTestPath, true));\n            do {\n                int byteCount = inputStream.read(buff);\n                if (byteCount == -1) {\n                    break;\n                }\n                bos.write(buff, 0, byteCount);\n\n                if (sleepMilliSec > 0 || sleepNanoSec > 0) {\n                    try {\n                        Thread.sleep(sleepMilliSec, sleepNanoSec);\n                    } catch (InterruptedException e) {\n                        e.printStackTrace();\n                    }\n                }\n            } while (true);\n\n        } catch (FileNotFoundException e) {\n            e.printStackTrace();\n        } catch (IOException e) {\n            e.printStackTrace();\n        } finally {\n            if (bos != null) {\n                try {\n                    bos.flush();\n                    bos.close();\n                } catch (IOException e) {\n                    e.printStackTrace();\n                }\n            }\n            if (inputStream != null) {\n                try {\n                    inputStream.close();\n                } catch (IOException e) {\n                    e.printStackTrace();\n                }\n            }\n        }\n\n        infoAppend(\"BufferOutputStream\", start);\n\n\n        RandomAccessFile raf = null;\n        inputStream = initPerformanceTest();\n        start = System.currentTimeMillis();\n\n        // ---------------------- RandomAccessFile\n        try {\n            raf = new RandomAccessFile(writePerformanceTestPath, \"rw\");\n            do {\n                int byteCount = inputStream.read(buff);\n                if (byteCount == -1) {\n                    break;\n                }\n                raf.write(buff, 0, byteCount);\n\n                if (sleepMilliSec > 0 || sleepNanoSec > 0) {\n                    try {\n                        Thread.sleep(sleepMilliSec, sleepNanoSec);\n                    } catch (InterruptedException e) {\n                        e.printStackTrace();\n                    }\n                }\n            } while (true);\n\n        } catch (FileNotFoundException e) {\n            e.printStackTrace();\n        } catch (IOException e) {\n            e.printStackTrace();\n        } finally {\n            if (raf != null) {\n                try {\n                    raf.close();\n                } catch (IOException e) {\n                    e.printStackTrace();\n                }\n            }\n            if (inputStream != null) {\n                try {\n                    inputStream.close();\n                } catch (IOException e) {\n                    e.printStackTrace();\n                }\n            }\n        }\n\n        infoAppend(\"RandomAccessFile\", start);\n\n        Sink sink = null;\n        inputStream = initPerformanceTest();\n        Source source = Okio.source(inputStream);\n        Buffer buffer = new Buffer();\n        start = System.currentTimeMillis();\n\n        try {\n            sink = Okio.sink(new File(writePerformanceTestPath));\n            sink = Okio.buffer(sink);\n\n            do {\n                long byteCount = source.read(buffer, BUFFER_SIZE);\n                if (byteCount == -1) {\n                    break;\n                }\n\n                sink.write(buffer, byteCount);\n\n                if (sleepMilliSec > 0 || sleepNanoSec > 0) {\n                    try {\n                        Thread.sleep(sleepMilliSec, sleepNanoSec);\n                    } catch (InterruptedException e) {\n                        e.printStackTrace();\n                    }\n                }\n            } while (true);\n\n        } catch (FileNotFoundException e) {\n            e.printStackTrace();\n        } catch (IOException e) {\n            e.printStackTrace();\n        } finally {\n            if (sink != null) {\n                try {\n                    sink.close();\n                } catch (IOException e) {\n                    e.printStackTrace();\n                }\n            }\n\n            if (inputStream != null) {\n                try {\n                    inputStream.close();\n                } catch (IOException e) {\n                    e.printStackTrace();\n                }\n            }\n        }\n\n        infoAppend(\"okio\", start);\n    }\n\n    private InputStream initPerformanceTest() {\n        try {\n            final File file = new File(writePerformanceTestPath);\n            if (file.exists()) {\n                file.delete();\n            }\n            file.createNewFile();\n            return getResources().getAssets().open(\"performance_test_data\");\n        } catch (IOException e) {\n            e.printStackTrace();\n        }\n        return null;\n    }\n\n    private void infoAppend(final String msg, final long start) {\n        infoTv.append(String.format(\" %s: %d\\n\", msg, System.currentTimeMillis() - start));\n        scrollView.post(new Runnable() {\n            @Override\n            public void run() {\n                if (scrollView != null) {\n                    scrollView.fullScroll(View.FOCUS_DOWN);\n                }\n            }\n        });\n    }\n\n    private AppCompatSeekBar ioPerformanceSb;\n    private ScrollView scrollView;\n    private TextView infoTv;\n\n    private void assignViews() {\n        ioPerformanceSb = (AppCompatSeekBar) findViewById(R.id.io_performance_sb);\n        scrollView = (ScrollView) findViewById(R.id.scrollView);\n        infoTv = (TextView) findViewById(R.id.info_tv);\n    }\n\n}\n"
  },
  {
    "path": "demo/src/main/res/drawable/bg_item_task_manager.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?><!--\n  ~ Copyright (c) 2015 LingoChamp Inc.\n  ~\n  ~ Licensed under the Apache License, Version 2.0 (the \"License\");\n  ~ you may not use this file except in compliance with the License.\n  ~ You may obtain a copy of the License at\n  ~\n  ~    http://www.apache.org/licenses/LICENSE-2.0\n  ~\n  ~ Unless required by applicable law or agreed to in writing, software\n  ~ distributed under the License is distributed on an \"AS IS\" BASIS,\n  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n  ~ See the License for the specific language governing permissions and\n  ~ limitations under the License.\n  -->\n\n<layer-list xmlns:android=\"http://schemas.android.com/apk/res/android\">\n    <item\n        android:left=\"-2dp\"\n        android:right=\"-2dp\"\n        android:top=\"-2dp\">\n        <shape android:shape=\"rectangle\">\n            <stroke\n                android:width=\"1px\"\n                android:color=\"@android:color/darker_gray\" />\n        </shape>\n    </item>\n</layer-list>"
  },
  {
    "path": "demo/src/main/res/layout/activity_hybrid_test.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<RelativeLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"match_parent\">\n\n    <LinearLayout\n        android:id=\"@+id/top_group\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"wrap_content\"\n        android:orientation=\"vertical\">\n\n        <Button\n            style=\"@style/DemoButton\"\n            android:layout_marginTop=\"10dp\"\n            android:onClick=\"onClickDel\"\n            android:text=\"@string/del_cache_files\" />\n\n        <Button\n            style=\"@style/DemoButton\"\n            android:onClick=\"onClickStartSingleDownload\"\n            android:text=\"@string/hybrid_test_single_task_title\" />\n\n        <Button\n            style=\"@style/DemoButton\"\n            android:onClick=\"onClickMultiSerial\"\n            android:text=\"@string/hybrid_test_multiple_tasks_serial_title\" />\n\n        <Button\n            style=\"@style/DemoButton\"\n            android:onClick=\"onClickMultiParallel\"\n            android:text=\"@string/hybrid_test_multiple_tasks_parallel_title\" />\n    </LinearLayout>\n\n    <ScrollView\n        android:id=\"@+id/scrollView\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"wrap_content\"\n        android:layout_below=\"@+id/top_group\">\n\n        <TextView\n            android:id=\"@+id/download_msg_tv\"\n            android:layout_width=\"match_parent\"\n            android:layout_height=\"wrap_content\"\n            android:padding=\"10dp\"\n            android:textColor=\"@color/colorPrimaryDark\"\n            android:textSize=\"12sp\" />\n    </ScrollView>\n\n    <TextView\n        android:id=\"@+id/tip_msg_tv\"\n        android:layout_width=\"wrap_content\"\n        android:layout_height=\"wrap_content\"\n        android:layout_alignParentBottom=\"true\"\n        android:layout_centerHorizontal=\"true\"\n        android:alpha=\"0.5\"\n        android:textColor=\"@color/colorAccent\"\n        android:textSize=\"16sp\" />\n</RelativeLayout>"
  },
  {
    "path": "demo/src/main/res/layout/activity_main.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<LinearLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"match_parent\"\n    android:orientation=\"vertical\">\n\n    <Button\n        style=\"@style/DemoButton\"\n        android:onClick=\"onClickMultitask\"\n        android:text=\"@string/multitask_test_title\" />\n\n    <Button\n        style=\"@style/DemoButton\"\n        android:onClick=\"onClickSingle\"\n        android:text=\"@string/single_task_test_title\" />\n\n    <Button\n        style=\"@style/DemoButton\"\n        android:onClick=\"onClickHybridTest\"\n        android:text=\"@string/hybrid_test_title\" />\n\n    <Button\n        style=\"@style/DemoButton\"\n        android:onClick=\"onClickTasksManager\"\n        android:text=\"@string/tasks_manager_demo_title\" />\n\n    <Button\n        style=\"@style/DemoButton\"\n        android:onClick=\"onClickPerformance\"\n        android:text=\"@string/performance_test_title\" />\n\n    <Button\n        style=\"@style/DemoButton\"\n        android:onClick=\"onClickNotification\"\n        android:text=\"@string/notification_demo_title\" />\n\n\n</LinearLayout>"
  },
  {
    "path": "demo/src/main/res/layout/activity_mutitask_test.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?><!--\n  ~ Copyright (c) 2015 LingoChamp Inc.\n  ~\n  ~ Licensed under the Apache License, Version 2.0 (the \"License\");\n  ~ you may not use this file except in compliance with the License.\n  ~ You may obtain a copy of the License at\n  ~\n  ~    http://www.apache.org/licenses/LICENSE-2.0\n  ~\n  ~ Unless required by applicable law or agreed to in writing, software\n  ~ distributed under the License is distributed on an \"AS IS\" BASIS,\n  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n  ~ See the License for the specific language governing permissions and\n  ~ limitations under the License.\n  -->\n\n<ScrollView xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"match_parent\"\n    android:orientation=\"vertical\">\n\n    <LinearLayout\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"wrap_content\"\n        android:orientation=\"vertical\"\n        android:padding=\"20dp\">\n\n\n        <SeekBar\n            android:id=\"@+id/task_count_sb\"\n            android:layout_width=\"match_parent\"\n            android:layout_height=\"wrap_content\" />\n\n        <LinearLayout\n            android:layout_width=\"match_parent\"\n            android:layout_height=\"wrap_content\"\n            android:layout_marginTop=\"5dp\"\n            android:gravity=\"center_vertical\"\n            android:orientation=\"horizontal\">\n\n            <TextView\n                android:id=\"@+id/task_count_tv\"\n                android:layout_width=\"0dp\"\n                android:layout_height=\"wrap_content\"\n                android:layout_marginLeft=\"10dp\"\n                android:layout_weight=\"1.0\"\n                android:text=\"0\"\n                android:textColor=\"@color/colorAccent\"\n                android:textSize=\"14sp\" />\n\n            <TextView\n                android:id=\"@+id/time_consume_tv\"\n                android:layout_width=\"0dp\"\n                android:layout_height=\"wrap_content\"\n                android:layout_weight=\"1.0\"\n                android:text=\"0\"\n                android:textColor=\"@color/colorAccent\"\n                android:textSize=\"14sp\" />\n\n            <RadioGroup\n                android:id=\"@+id/way_rgp\"\n                android:layout_width=\"wrap_content\"\n                android:layout_height=\"wrap_content\"\n                android:orientation=\"horizontal\">\n\n                <RadioButton\n                    android:id=\"@+id/serial_rbtn\"\n                    android:layout_width=\"wrap_content\"\n                    android:layout_height=\"wrap_content\"\n                    android:checked=\"true\"\n                    android:text=\"Serial\"\n                    android:textColor=\"@android:color/black\"\n                    android:textSize=\"14sp\" />\n\n                <RadioButton\n                    android:id=\"@+id/parallel_rbtn\"\n                    android:layout_width=\"wrap_content\"\n                    android:layout_height=\"wrap_content\"\n                    android:text=\"Parallel\"\n                    android:textColor=\"@android:color/black\"\n                    android:textSize=\"14sp\" />\n            </RadioGroup>\n\n\n        </LinearLayout>\n\n        <CheckBox\n            android:id=\"@+id/avoid_miss_frame_cb\"\n            android:layout_width=\"wrap_content\"\n            android:layout_height=\"wrap_content\"\n            android:layout_marginTop=\"5dp\"\n            android:checked=\"true\"\n            android:text=\"@string/avoid_missing_screen_frames\"\n            android:maxLines=\"1\"\n            android:textColor=\"@color/colorAccent\"\n            android:textSize=\"14sp\" />\n\n        <LinearLayout\n            android:layout_width=\"match_parent\"\n            android:layout_height=\"wrap_content\"\n            android:layout_marginTop=\"5dp\"\n            android:gravity=\"center_vertical\"\n            android:orientation=\"horizontal\">\n\n            <ProgressBar\n                android:id=\"@+id/over_task_pb\"\n                style=\"?android:attr/progressBarStyleHorizontal\"\n                android:layout_width=\"0dp\"\n                android:layout_height=\"wrap_content\"\n                android:layout_weight=\"1.0\" />\n\n            <Button\n                android:id=\"@+id/action_btn\"\n                android:layout_width=\"wrap_content\"\n                android:layout_height=\"wrap_content\"\n                android:layout_marginLeft=\"5dp\"\n                android:text=\"@string/start\"\n                android:textSize=\"14sp\" />\n        </LinearLayout>\n\n\n        <TextView\n            android:id=\"@+id/pending_tv\"\n            style=\"@style/DemoInfoTextStyle\"\n            android:text=\"@string/multitask_test_pending\" />\n\n        <TextView\n            android:id=\"@+id/pending_info_tv\"\n            style=\"@style/DemoTasksTextStyle\"\n            android:text=\"\" />\n\n        <ProgressBar\n            android:id=\"@+id/pending_pb\"\n            style=\"?android:attr/progressBarStyleHorizontal\"\n            android:layout_width=\"match_parent\"\n            android:layout_height=\"wrap_content\" />\n\n        <TextView\n            android:id=\"@+id/connected_tv\"\n            style=\"@style/DemoInfoTextStyle\"\n            android:text=\"@string/multitask_test_connected\" />\n\n        <TextView\n            android:id=\"@+id/connected_info_tv\"\n            style=\"@style/DemoTasksTextStyle\"\n            android:text=\"\" />\n\n        <ProgressBar\n            android:id=\"@+id/connected_pb\"\n            style=\"?android:attr/progressBarStyleHorizontal\"\n            android:layout_width=\"match_parent\"\n            android:layout_height=\"wrap_content\" />\n\n        <TextView\n            android:id=\"@+id/progress_tv\"\n            style=\"@style/DemoInfoTextStyle\"\n            android:layout_width=\"match_parent\"\n            android:text=\"@string/multitask_test_progress\"\n            android:visibility=\"gone\" />\n\n        <TextView\n            android:id=\"@+id/progress_info_tv\"\n            style=\"@style/DemoTasksTextStyle\"\n            android:layout_width=\"match_parent\"\n            android:text=\"\"\n            android:visibility=\"gone\" />\n\n        <ProgressBar\n            android:id=\"@+id/progress_pb\"\n            style=\"?android:attr/progressBarStyleHorizontal\"\n            android:layout_width=\"match_parent\"\n            android:layout_height=\"wrap_content\"\n            android:visibility=\"gone\" />\n\n        <TextView\n            android:id=\"@+id/retry_tv\"\n            style=\"@style/DemoInfoTextStyle\"\n            android:layout_width=\"match_parent\"\n            android:text=\"@string/multitask_test_retry\" />\n\n        <TextView\n            android:id=\"@+id/retry_info_tv\"\n            style=\"@style/DemoTasksTextStyle\"\n            android:layout_width=\"match_parent\"\n            android:text=\"\" />\n\n        <ProgressBar\n            android:id=\"@+id/retry_pb\"\n            style=\"?android:attr/progressBarStyleHorizontal\"\n            android:layout_width=\"match_parent\"\n            android:layout_height=\"wrap_content\" />\n\n        <TextView\n            android:id=\"@+id/error_tv\"\n            style=\"@style/DemoInfoTextStyle\"\n            android:text=\"@string/multitask_test_error\" />\n\n        <TextView\n            android:id=\"@+id/error_info_tv\"\n            style=\"@style/DemoTasksTextStyle\"\n            android:text=\"\" />\n\n        <ProgressBar\n            android:id=\"@+id/error_pb\"\n            style=\"?android:attr/progressBarStyleHorizontal\"\n            android:layout_width=\"match_parent\"\n            android:layout_height=\"wrap_content\" />\n\n        <TextView\n            android:id=\"@+id/paused_tv\"\n            style=\"@style/DemoInfoTextStyle\"\n            android:text=\"@string/multitask_test_paused\" />\n\n        <TextView\n            android:id=\"@+id/paused_info_tv\"\n            style=\"@style/DemoTasksTextStyle\"\n            android:text=\"\" />\n\n        <ProgressBar\n            android:id=\"@+id/paused_pb\"\n            style=\"?android:attr/progressBarStyleHorizontal\"\n            android:layout_width=\"match_parent\"\n            android:layout_height=\"wrap_content\" />\n\n        <TextView\n            android:id=\"@+id/completed_with_old_tv\"\n            style=\"@style/DemoInfoTextStyle\"\n            android:text=\"@string/multitask_test_completed_reused\" />\n\n        <TextView\n            android:id=\"@+id/completed_with_old_info_tv\"\n            style=\"@style/DemoTasksTextStyle\"\n            android:text=\"\" />\n\n        <ProgressBar\n            android:id=\"@+id/completed_with_old_pb\"\n            style=\"?android:attr/progressBarStyleHorizontal\"\n            android:layout_width=\"match_parent\"\n            android:layout_height=\"wrap_content\" />\n\n        <TextView\n            android:id=\"@+id/completed_tv\"\n            style=\"@style/DemoInfoTextStyle\"\n            android:text=\"@string/multitask_test_completed_downloading\" />\n\n        <TextView\n            android:id=\"@+id/completed_info_tv\"\n            style=\"@style/DemoTasksTextStyle\"\n            android:text=\"\" />\n\n        <ProgressBar\n            android:id=\"@+id/completed_pb\"\n            style=\"?android:attr/progressBarStyleHorizontal\"\n            android:layout_width=\"match_parent\"\n            android:layout_height=\"wrap_content\" />\n\n        <TextView\n            android:id=\"@+id/warn_tv\"\n            style=\"@style/DemoInfoTextStyle\"\n            android:text=\"@string/multitask_test_warn\" />\n\n        <TextView\n            android:id=\"@+id/warn_info_tv\"\n            style=\"@style/DemoTasksTextStyle\"\n            android:text=\"\" />\n\n        <ProgressBar\n            android:id=\"@+id/warn_pb\"\n            style=\"?android:attr/progressBarStyleHorizontal\"\n            android:layout_width=\"match_parent\"\n            android:layout_height=\"wrap_content\" />\n\n        <Button\n            android:id=\"@+id/delete_all_file_btn\"\n            style=\"@style/DemoButton\"\n            android:layout_width=\"match_parent\"\n            android:layout_height=\"wrap_content\"\n            android:text=\"@string/del_cache_files\" />\n    </LinearLayout>\n</ScrollView>"
  },
  {
    "path": "demo/src/main/res/layout/activity_notification_minset.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?><!--\n  ~ Copyright (c) 2015 LingoChamp Inc.\n  ~\n  ~ Licensed under the Apache License, Version 2.0 (the \"License\");\n  ~ you may not use this file except in compliance with the License.\n  ~ You may obtain a copy of the License at\n  ~\n  ~    http://www.apache.org/licenses/LICENSE-2.0\n  ~\n  ~ Unless required by applicable law or agreed to in writing, software\n  ~ distributed under the License is distributed on an \"AS IS\" BASIS,\n  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n  ~ See the License for the specific language governing permissions and\n  ~ limitations under the License.\n  -->\n\n<LinearLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"match_parent\"\n    android:background=\"@android:color/white\"\n    android:orientation=\"vertical\"\n    android:padding=\"10dp\">\n\n    <TextView\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"wrap_content\"\n        android:layout_marginTop=\"5dp\"\n        android:text=\"@string/notification_min_set_desc\"\n        android:textColor=\"@color/colorAccent\"\n        android:textSize=\"16sp\" />\n\n    <LinearLayout\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"wrap_content\"\n        android:layout_marginTop=\"5dp\"\n        android:orientation=\"horizontal\">\n\n        <Button\n            android:layout_width=\"0dp\"\n            android:layout_height=\"wrap_content\"\n            android:layout_weight=\"1.0\"\n            android:onClick=\"onClickStart\"\n            android:text=\"@string/start\"\n            android:textColor=\"#000000\"\n            android:textSize=\"14sp\" />\n\n        <Button\n            android:layout_width=\"0dp\"\n            android:layout_height=\"wrap_content\"\n            android:layout_weight=\"1.0\"\n            android:onClick=\"onClickPause\"\n            android:text=\"@string/pause\"\n            android:textColor=\"#000000\"\n            android:textSize=\"14sp\" />\n\n        <Button\n            android:layout_width=\"0dp\"\n            android:layout_height=\"wrap_content\"\n            android:layout_weight=\"1.0\"\n            android:onClick=\"onClickDelete\"\n            android:text=\"@string/delete\"\n            android:textColor=\"#000000\"\n            android:textSize=\"14sp\" />\n    </LinearLayout>\n\n\n</LinearLayout>"
  },
  {
    "path": "demo/src/main/res/layout/activity_notification_sample.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?><!--\n  ~ Copyright (c) 2015 LingoChamp Inc.\n  ~\n  ~ Licensed under the Apache License, Version 2.0 (the \"License\");\n  ~ you may not use this file except in compliance with the License.\n  ~ You may obtain a copy of the License at\n  ~\n  ~    http://www.apache.org/licenses/LICENSE-2.0\n  ~\n  ~ Unless required by applicable law or agreed to in writing, software\n  ~ distributed under the License is distributed on an \"AS IS\" BASIS,\n  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n  ~ See the License for the specific language governing permissions and\n  ~ limitations under the License.\n  -->\n\n<LinearLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"match_parent\"\n    android:background=\"@android:color/white\"\n    android:orientation=\"vertical\"\n    android:padding=\"10dp\">\n\n    <TextView\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"wrap_content\"\n        android:layout_marginTop=\"5dp\"\n        android:text=\"@string/notification_sample_desc\"\n        android:textColor=\"@color/colorAccent\"\n        android:textSize=\"16sp\" />\n\n    <LinearLayout\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"wrap_content\"\n        android:layout_marginTop=\"5dp\"\n        android:orientation=\"horizontal\">\n\n        <Button\n            android:layout_width=\"0dp\"\n            android:layout_height=\"wrap_content\"\n            android:layout_weight=\"1.0\"\n            android:onClick=\"onClickStart\"\n            android:text=\"@string/start\"\n            android:textColor=\"#000000\"\n            android:textSize=\"14sp\"\n            android:id=\"@+id/view_start\"/>\n\n        <Button\n            android:layout_width=\"0dp\"\n            android:layout_height=\"wrap_content\"\n            android:layout_weight=\"1.0\"\n            android:onClick=\"onClickPause\"\n            android:text=\"@string/pause\"\n            android:textColor=\"#000000\"\n            android:textSize=\"14sp\" />\n\n        <Button\n            android:layout_width=\"0dp\"\n            android:layout_height=\"wrap_content\"\n            android:layout_weight=\"1.0\"\n            android:onClick=\"onClickDelete\"\n            android:text=\"@string/delete\"\n            android:textColor=\"#000000\"\n            android:textSize=\"14sp\" />\n    </LinearLayout>\n\n\n    <CheckBox\n        android:id=\"@+id/show_notification_cb\"\n        android:layout_width=\"wrap_content\"\n        android:layout_height=\"wrap_content\"\n        android:checked=\"true\"\n        android:text=\"@string/notification_sample_show\"\n        android:textColor=\"@android:color/black\"\n        android:textSize=\"16sp\" />\n\n    <ProgressBar\n        android:id=\"@+id/progressBar\"\n        style=\"?android:attr/progressBarStyleHorizontal\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"wrap_content\"\n        android:layout_gravity=\"center_horizontal\"\n        android:layout_margin=\"5dp\" />\n\n</LinearLayout>"
  },
  {
    "path": "demo/src/main/res/layout/activity_performance.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?><!--\n  ~ Copyright (c) 2015 LingoChamp Inc.\n  ~\n  ~ Licensed under the Apache License, Version 2.0 (the \"License\");\n  ~ you may not use this file except in compliance with the License.\n  ~ You may obtain a copy of the License at\n  ~\n  ~    http://www.apache.org/licenses/LICENSE-2.0\n  ~\n  ~ Unless required by applicable law or agreed to in writing, software\n  ~ distributed under the License is distributed on an \"AS IS\" BASIS,\n  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n  ~ See the License for the specific language governing permissions and\n  ~ limitations under the License.\n  -->\n\n<LinearLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"match_parent\"\n    android:orientation=\"vertical\">\n\n    <TextView\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"wrap_content\"\n        android:layout_marginLeft=\"10dp\"\n        android:layout_marginTop=\"10dp\"\n        android:text=\"@string/performance_test_parcel\"\n        android:textColor=\"@color/colorAccent\" />\n\n    <Button\n        style=\"@style/DemoButton\"\n        android:onClick=\"onClickLongOperate\"\n        android:text=\"Long Operate\" />\n\n    <Button\n        style=\"@style/DemoButton\"\n        android:onClick=\"onClickIntOperate\"\n        android:text=\"Int Operate\" />\n\n    <Button\n        style=\"@style/DemoButton\"\n        android:onClick=\"onClickLongParcel\"\n        android:text=\"Long Parcel\" />\n\n    <Button\n        style=\"@style/DemoButton\"\n        android:onClick=\"onClickIntParcel\"\n        android:text=\"Int Parcel\" />\n\n    <TextView\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"wrap_content\"\n        android:layout_marginLeft=\"10dp\"\n        android:layout_marginTop=\"10dp\"\n        android:text=\"@string/performance_test_output\"\n        android:textColor=\"@color/colorAccent\" />\n\n    <android.support.v7.widget.AppCompatSeekBar\n        android:id=\"@+id/io_performance_sb\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"wrap_content\"\n        android:progress=\"5\"\n        android:max=\"50\" />\n\n    <Button\n        style=\"@style/DemoButton\"\n        android:onClick=\"onClickWriteTest\"\n        android:text=\"Output performance test\" />\n\n    <ScrollView\n        android:id=\"@+id/scrollView\"\n        android:layout_width=\"wrap_content\"\n        android:layout_height=\"wrap_content\"\n        android:scrollbars=\"none\">\n\n        <TextView\n            android:id=\"@+id/info_tv\"\n            android:layout_width=\"match_parent\"\n            android:layout_height=\"wrap_content\"\n            android:gravity=\"right\"\n            android:padding=\"10dp\"\n            android:textColor=\"@color/colorPrimary\"\n            android:textSize=\"16sp\" />\n    </ScrollView>\n</LinearLayout>"
  },
  {
    "path": "demo/src/main/res/layout/activity_single.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<ScrollView xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"match_parent\">\n\n    <LinearLayout\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"wrap_content\"\n        android:background=\"#ffffff\"\n        android:orientation=\"vertical\"\n        android:padding=\"10dp\">\n\n        <TextView\n            android:layout_width=\"wrap_content\"\n            android:layout_height=\"wrap_content\"\n            android:text=\"File path = Dir Path / Content-Disposition-filename\"\n            android:textColor=\"@color/colorAccent\"\n            android:textSize=\"16sp\" />\n\n        <LinearLayout\n            android:layout_width=\"match_parent\"\n            android:layout_height=\"wrap_content\"\n            android:layout_marginTop=\"5dp\"\n            android:orientation=\"horizontal\">\n\n            <Button\n                android:id=\"@+id/start_btn_1\"\n                android:layout_width=\"0dp\"\n                android:layout_height=\"wrap_content\"\n                android:layout_weight=\"1.0\"\n                android:text=\"@string/start\"\n                android:textColor=\"#000000\"\n                android:textSize=\"14sp\" />\n\n            <Button\n                android:id=\"@+id/pause_btn_1\"\n                android:layout_width=\"0dp\"\n                android:layout_height=\"wrap_content\"\n                android:layout_weight=\"1.0\"\n                android:text=\"@string/pause\"\n                android:textColor=\"#000000\"\n                android:textSize=\"14sp\" />\n\n            <Button\n                android:id=\"@+id/delete_btn_1\"\n                android:layout_width=\"0dp\"\n                android:layout_height=\"wrap_content\"\n                android:layout_weight=\"1.0\"\n                android:text=\"@string/delete\"\n                android:textColor=\"#000000\"\n                android:textSize=\"14sp\" />\n        </LinearLayout>\n\n\n        <TextView\n            android:id=\"@+id/filename_tv_1\"\n            android:layout_width=\"wrap_content\"\n            android:layout_height=\"wrap_content\"\n            android:layout_gravity=\"center\"\n            android:text=\"-\"\n            android:textColor=\"@color/colorPrimary\"\n            android:textSize=\"14sp\" />\n\n        <TextView\n            android:id=\"@+id/speed_tv_1\"\n            android:layout_width=\"match_parent\"\n            android:layout_height=\"wrap_content\"\n            android:gravity=\"center_horizontal\"\n            android:textColor=\"#000000\"\n            android:textSize=\"14sp\" />\n\n        <ProgressBar\n            android:id=\"@+id/progressBar_1\"\n            style=\"?android:attr/progressBarStyleHorizontal\"\n            android:layout_width=\"match_parent\"\n            android:layout_height=\"wrap_content\"\n            android:layout_gravity=\"center_horizontal\"\n            android:layout_margin=\"5dp\" />\n\n        <LinearLayout\n            android:layout_width=\"match_parent\"\n            android:layout_height=\"wrap_content\"\n            android:orientation=\"horizontal\">\n\n            <Button\n                android:id=\"@+id/start_btn_2\"\n                android:layout_width=\"0dp\"\n                android:layout_height=\"wrap_content\"\n                android:layout_weight=\"1.0\"\n                android:text=\"@string/start\"\n                android:textColor=\"#000000\"\n                android:textSize=\"14sp\" />\n\n            <Button\n                android:id=\"@+id/pause_btn_2\"\n                android:layout_width=\"0dp\"\n                android:layout_height=\"wrap_content\"\n                android:layout_weight=\"1.0\"\n                android:text=\"@string/pause\"\n                android:textColor=\"#000000\"\n                android:textSize=\"14sp\" />\n\n            <Button\n                android:id=\"@+id/delete_btn_2\"\n                android:layout_width=\"0dp\"\n                android:layout_height=\"wrap_content\"\n                android:layout_weight=\"1.0\"\n                android:text=\"@string/delete\"\n                android:textColor=\"#000000\"\n                android:textSize=\"14sp\" />\n        </LinearLayout>\n\n        <TextView\n            android:id=\"@+id/filename_tv_2\"\n            android:layout_width=\"wrap_content\"\n            android:layout_height=\"wrap_content\"\n            android:layout_gravity=\"center\"\n            android:text=\"-\"\n            android:textColor=\"@color/colorPrimary\"\n            android:textSize=\"14sp\" />\n\n        <TextView\n            android:id=\"@+id/speed_tv_2\"\n            android:layout_width=\"match_parent\"\n            android:layout_height=\"wrap_content\"\n            android:gravity=\"center_horizontal\"\n            android:textColor=\"#000000\"\n            android:textSize=\"14sp\" />\n\n        <ProgressBar\n            android:id=\"@+id/progressBar_2\"\n            style=\"?android:attr/progressBarStyleHorizontal\"\n            android:layout_width=\"match_parent\"\n            android:layout_height=\"wrap_content\"\n            android:layout_gravity=\"center_horizontal\"\n            android:layout_margin=\"5dp\" />\n\n        <TextView\n            android:layout_width=\"wrap_content\"\n            android:layout_height=\"wrap_content\"\n            android:text=\"Normal Task\"\n            android:textColor=\"@color/colorAccent\"\n            android:textSize=\"16sp\" />\n\n        <LinearLayout\n            android:layout_width=\"match_parent\"\n            android:layout_height=\"wrap_content\"\n            android:layout_marginTop=\"5dp\"\n            android:orientation=\"horizontal\">\n\n            <Button\n                android:id=\"@+id/start_btn_3\"\n                android:layout_width=\"0dp\"\n                android:layout_height=\"wrap_content\"\n                android:layout_weight=\"1.0\"\n                android:text=\"@string/start\"\n                android:textColor=\"#000000\"\n                android:textSize=\"14sp\" />\n\n            <Button\n                android:id=\"@+id/pause_btn_3\"\n                android:layout_width=\"0dp\"\n                android:layout_height=\"wrap_content\"\n                android:layout_weight=\"1.0\"\n                android:text=\"@string/pause\"\n                android:textColor=\"#000000\"\n                android:textSize=\"14sp\" />\n\n            <Button\n                android:id=\"@+id/delete_btn_3\"\n                android:layout_width=\"0dp\"\n                android:layout_height=\"wrap_content\"\n                android:layout_weight=\"1.0\"\n                android:text=\"@string/delete\"\n                android:textColor=\"#000000\"\n                android:textSize=\"14sp\" />\n        </LinearLayout>\n\n\n        <TextView\n            android:id=\"@+id/speed_tv_3\"\n            android:layout_width=\"match_parent\"\n            android:layout_height=\"wrap_content\"\n            android:gravity=\"center_horizontal\"\n            android:textColor=\"#000000\"\n            android:textSize=\"14sp\" />\n\n        <ProgressBar\n            android:id=\"@+id/progressBar_3\"\n            style=\"?android:attr/progressBarStyleHorizontal\"\n            android:layout_width=\"match_parent\"\n            android:layout_height=\"wrap_content\"\n            android:layout_gravity=\"center_horizontal\"\n            android:layout_margin=\"5dp\" />\n\n        <TextView\n            android:layout_width=\"wrap_content\"\n            android:layout_height=\"wrap_content\"\n            android:layout_marginTop=\"20dp\"\n            android:text=\"Chunked http transfer Task\"\n            android:textColor=\"@color/colorAccent\"\n            android:textSize=\"16sp\" />\n\n        <LinearLayout\n            android:layout_width=\"match_parent\"\n            android:layout_height=\"wrap_content\"\n            android:layout_marginTop=\"5dp\"\n            android:orientation=\"horizontal\">\n\n            <Button\n                android:id=\"@+id/start_btn_4\"\n                android:layout_width=\"0dp\"\n                android:layout_height=\"wrap_content\"\n                android:layout_weight=\"1.0\"\n                android:text=\"@string/start\"\n                android:textColor=\"#000000\"\n                android:textSize=\"14sp\" />\n\n            <Button\n                android:id=\"@+id/pause_btn_4\"\n                android:layout_width=\"0dp\"\n                android:layout_height=\"wrap_content\"\n                android:layout_weight=\"1.0\"\n                android:text=\"@string/pause\"\n                android:textColor=\"#000000\"\n                android:textSize=\"14sp\" />\n\n            <Button\n                android:id=\"@+id/delete_btn_4\"\n                android:layout_width=\"0dp\"\n                android:layout_height=\"wrap_content\"\n                android:layout_weight=\"1.0\"\n                android:text=\"@string/delete\"\n                android:textColor=\"#000000\"\n                android:textSize=\"14sp\" />\n        </LinearLayout>\n\n        <TextView\n            android:id=\"@+id/detail_tv_4\"\n            android:layout_width=\"wrap_content\"\n            android:layout_height=\"wrap_content\"\n            android:layout_gravity=\"center\"\n            android:text=\"-\"\n            android:textColor=\"@color/colorPrimary\"\n            android:textSize=\"14sp\" />\n\n\n        <TextView\n            android:id=\"@+id/speed_tv_4\"\n            android:layout_width=\"match_parent\"\n            android:layout_height=\"wrap_content\"\n            android:gravity=\"center_horizontal\"\n            android:textColor=\"#000000\"\n            android:textSize=\"14sp\" />\n\n        <ProgressBar\n            android:id=\"@+id/progressBar_4\"\n            style=\"?android:attr/progressBarStyleHorizontal\"\n            android:layout_width=\"match_parent\"\n            android:layout_height=\"wrap_content\"\n            android:layout_gravity=\"center_horizontal\"\n            android:layout_marginLeft=\"5dp\"\n            android:layout_marginRight=\"5dp\" />\n\n    </LinearLayout>\n</ScrollView>"
  },
  {
    "path": "demo/src/main/res/layout/activity_tasks_manager_demo.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?><!--\n  ~ Copyright (c) 2015 LingoChamp Inc.\n  ~\n  ~ Licensed under the Apache License, Version 2.0 (the \"License\");\n  ~ you may not use this file except in compliance with the License.\n  ~ You may obtain a copy of the License at\n  ~\n  ~    http://www.apache.org/licenses/LICENSE-2.0\n  ~\n  ~ Unless required by applicable law or agreed to in writing, software\n  ~ distributed under the License is distributed on an \"AS IS\" BASIS,\n  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n  ~ See the License for the specific language governing permissions and\n  ~ limitations under the License.\n  -->\n\n<android.support.v7.widget.RecyclerView xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:id=\"@+id/recycler_view\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"match_parent\" />\n"
  },
  {
    "path": "demo/src/main/res/layout/item_tasks_manager.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?><!--\n  ~ Copyright (c) 2015 LingoChamp Inc.\n  ~\n  ~ Licensed under the Apache License, Version 2.0 (the \"License\");\n  ~ you may not use this file except in compliance with the License.\n  ~ You may obtain a copy of the License at\n  ~\n  ~    http://www.apache.org/licenses/LICENSE-2.0\n  ~\n  ~ Unless required by applicable law or agreed to in writing, software\n  ~ distributed under the License is distributed on an \"AS IS\" BASIS,\n  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n  ~ See the License for the specific language governing permissions and\n  ~ limitations under the License.\n  -->\n\n<LinearLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"wrap_content\"\n    android:background=\"@drawable/bg_item_task_manager\"\n    android:gravity=\"center_vertical\"\n    android:orientation=\"horizontal\"\n    android:padding=\"15dp\">\n\n    <LinearLayout\n        android:layout_width=\"0dp\"\n        android:layout_height=\"wrap_content\"\n        android:layout_weight=\"1.0\"\n        android:orientation=\"vertical\">\n\n        <TextView\n            android:id=\"@+id/task_name_tv\"\n            android:layout_width=\"wrap_content\"\n            android:layout_height=\"wrap_content\"\n            android:text=\"task: asdfslkdjflsjfo\"\n            android:textColor=\"@color/colorAccent\"\n            android:textSize=\"16sp\" />\n\n        <TextView\n            android:id=\"@+id/task_status_tv\"\n            android:layout_width=\"wrap_content\"\n            android:layout_height=\"wrap_content\"\n            android:layout_marginTop=\"1dp\"\n            android:text=\"status: connected\"\n            android:textColor=\"@android:color/secondary_text_dark\"\n            android:textSize=\"16sp\" />\n\n        <ProgressBar\n            android:id=\"@+id/task_pb\"\n            style=\"?android:attr/progressBarStyleHorizontal\"\n            android:layout_width=\"match_parent\"\n            android:layout_height=\"wrap_content\"\n            android:layout_marginTop=\"5dp\"\n            android:layout_weight=\"1.0\" />\n\n\n    </LinearLayout>\n\n    <Button\n        android:id=\"@+id/task_action_btn\"\n        android:layout_width=\"wrap_content\"\n        android:layout_height=\"wrap_content\"\n        android:layout_marginLeft=\"10dp\"\n        android:text=\"Start\"\n        android:textColor=\"@android:color/black\"\n        android:textSize=\"14sp\" />\n\n\n</LinearLayout>"
  },
  {
    "path": "demo/src/main/res/menu/menu_main.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<menu xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:app=\"http://schemas.android.com/apk/res-auto\">\n\n    <item\n        android:id=\"@+id/menu_github\"\n        android:icon=\"@null\"\n        android:title=\"@string/app_github\"\n        app:showAsAction=\"always\" />\n\n</menu>"
  },
  {
    "path": "demo/src/main/res/values/colors.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n    <color name=\"colorPrimary\">#3F51B5</color>\n    <color name=\"colorPrimaryDark\">#303F9F</color>\n    <color name=\"colorAccent\">#FF4081</color>\n</resources>\n"
  },
  {
    "path": "demo/src/main/res/values/donottranslate.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<!--\n  ~ Copyright (c) 2015 LingoChamp Inc.\n  ~\n  ~ Licensed under the Apache License, Version 2.0 (the \"License\");\n  ~ you may not use this file except in compliance with the License.\n  ~ You may obtain a copy of the License at\n  ~\n  ~    http://www.apache.org/licenses/LICENSE-2.0\n  ~\n  ~ Unless required by applicable law or agreed to in writing, software\n  ~ distributed under the License is distributed on an \"AS IS\" BASIS,\n  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n  ~ See the License for the specific language governing permissions and\n  ~ limitations under the License.\n  -->\n\n<resources>\n    <string name=\"app_github\">GITHUB</string>\n    <string name=\"app_github_url\">https://github.com/lingochamp/FileDownloader</string>\n\n    <!-- multitask_test, callback -->\n    <string name=\"multitask_test_pending\" formatted=\"false\">pending: %d</string>\n    <string name=\"multitask_test_connected\" formatted=\"false\">connected: %d</string>\n    <string name=\"multitask_test_progress\" formatted=\"false\">progress: %d</string>\n    <string name=\"multitask_test_retry\" formatted=\"false\">retry: %d</string>\n    <string name=\"multitask_test_error\" formatted=\"false\">error: %d</string>\n    <string name=\"multitask_test_paused\" formatted=\"false\">paused: %d</string>\n    <string name=\"multitask_test_warn\" formatted=\"false\">warn: %d</string>\n    <string name=\"multitask_test_completed_reused\" formatted=\"false\">completed, reuse the existed file: %d</string>\n    <string name=\"multitask_test_completed_downloading\" formatted=\"false\">completed, downloading: %d</string>\n</resources>"
  },
  {
    "path": "demo/src/main/res/values/strings.xml",
    "content": "<resources>\n    <string name=\"app_name\">FileDownloader Demo</string>\n\n    <string name=\"start\">Start</string>\n    <string name=\"pause\">Pause</string>\n    <string name=\"delete\">Delete</string>\n\n    <string name=\"del_cache_files\">Delete all downloaded files</string>\n    <string name=\"del_file_error_empty\">Empty folder already</string>\n\n    <string name=\"single_task_test_title\">Single task test</string>\n    <string name=\"performance_test_title\">Performance test</string>\n    <string name=\"performance_test_parcel\">100000(times) Basic performance test</string>\n    <string name=\"performance_test_output\">Output performance test</string>\n\n\n    <string name=\"tasks_manager_demo_title\">Tasks Manager Demo</string>\n    <string name=\"tasks_manager_demo_name\" formatted=\"false\">task: %d</string>\n    <string name=\"tasks_manager_demo_status_loading\">Status: loading...</string>\n    <string name=\"tasks_manager_demo_status_pending\">Status: pending</string>\n    <string name=\"tasks_manager_demo_status_started\">Status: started</string>\n    <string name=\"tasks_manager_demo_status_connected\">Status: connected</string>\n    <string name=\"tasks_manager_demo_status_progress\">Status: progressing</string>\n    <string name=\"tasks_manager_demo_status_downloading\" formatted=\"false\">Status: downloading %d</string>\n    <string name=\"tasks_manager_demo_status_error\">Status: error</string>\n    <string name=\"tasks_manager_demo_status_paused\">Status: paused</string>\n    <string name=\"tasks_manager_demo_status_completed\">Status: completed</string>\n    <string name=\"tasks_manager_demo_status_not_downloaded\">Status: has not downloaded</string>\n\n    <!-- hybrid test -->\n    <string name=\"hybrid_test_title\">Hybrid test</string>\n    <string name=\"hybrid_test_deleted_file\" formatted=\"false\">Deleted: %s</string>\n    <string name=\"hybrid_test_start_single_task\" formatted=\"false\">Start single task: %s</string>\n    <string name=\"hybrid_test_start_multiple_tasks_parallel\" formatted=\"false\">Start %d tasks to download in parallel</string>\n    <string name=\"hybrid_test_start_multiple_tasks_serial\" formatted=\"false\">Start %d tasks to download linearly</string>\n    <string name=\"hybrid_test_multiple_tasks_parallel_title\">Execute multiple tasks in parallel</string>\n    <string name=\"hybrid_test_multiple_tasks_serial_title\">Execute multiple tasks linearly</string>\n    <string name=\"hybrid_test_single_task_title\">Execute single task</string>\n    <string name=\"avoid_missing_screen_frames\">Avoid Missing Screen Frames</string>\n\n    <!-- notification demo -->\n    <string name=\"notification_demo_title\">Notification Demo</string>\n\n    <string name=\"notification_sample_title\">Notification Sample Demo</string>\n    <string name=\"notification_sample_desc\">This is notification sample demo, in this demo, we show the downloading information in the notification panel and Activity. Every downloading task has a notification item in the notification panel and we will hold them after finishing download. In the meantime, there is a indeterminate progress bar in the activity to indicate the whole process of download.</string>\n    <string name=\"notification_sample_show\">Show the Notification</string>\n\n    <string name=\"notification_min_set_title\">Notification MinSet Demo</string>\n    <string name=\"notification_min_set_desc\">This is notification min set demo, it means that we use as little as possible code to bind notification with downloading tasks.</string>\n\n    <!-- multitask test -->\n    <string name=\"multitask_test_title\">Multiple tasks test</string>\n\n</resources>\n"
  },
  {
    "path": "demo/src/main/res/values/styles.xml",
    "content": "<resources>\n\n    <!-- Base application theme. -->\n    <style name=\"AppTheme\" parent=\"Theme.AppCompat.Light.DarkActionBar\">\n        <!-- Customize your theme here. -->\n        <item name=\"colorPrimary\">@color/colorPrimary</item>\n        <item name=\"colorPrimaryDark\">@color/colorPrimaryDark</item>\n        <item name=\"colorAccent\">@color/colorAccent</item>\n    </style>\n\n    <style name=\"DemoButton\">\n        <item name=\"android:layout_width\">match_parent</item>\n        <item name=\"android:layout_height\">wrap_content</item>\n        <item name=\"android:padding\">10dp</item>\n        <item name=\"android:layout_marginTop\">5dp</item>\n        <item name=\"android:textSize\">14sp</item>\n        <item name=\"android:textColor\">@android:color/black</item>\n    </style>\n\n    <style name=\"DemoInfoTextStyle\">\n        <item name=\"android:layout_width\">wrap_content</item>\n        <item name=\"android:layout_height\">wrap_content</item>\n        <item name=\"android:layout_marginTop\">10dp</item>\n        <item name=\"android:textSize\">16sp</item>\n        <item name=\"android:textColor\">@android:color/black</item>\n    </style>\n\n    <style name=\"DemoTasksTextStyle\" parent=\"DemoInfoTextStyle\">\n        <item name=\"android:textSize\">10sp</item>\n        <item name=\"android:layout_marginTop\">0dp</item>\n        <item name=\"android:textColor\">@color/colorPrimaryDark</item>\n    </style>\n</resources>\n"
  },
  {
    "path": "demo/src/main/res/values-zh/strings.xml",
    "content": "<!--\n  ~ Copyright (c) 2015 LingoChamp Inc.\n  ~\n  ~ Licensed under the Apache License, Version 2.0 (the \"License\");\n  ~ you may not use this file except in compliance with the License.\n  ~ You may obtain a copy of the License at\n  ~\n  ~    http://www.apache.org/licenses/LICENSE-2.0\n  ~\n  ~ Unless required by applicable law or agreed to in writing, software\n  ~ distributed under the License is distributed on an \"AS IS\" BASIS,\n  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n  ~ See the License for the specific language governing permissions and\n  ~ limitations under the License.\n  -->\n\n<resources>\n    <string name=\"app_name\">文件下载引擎</string>\n\n    <string name=\"start\">开始</string>\n    <string name=\"pause\">暂停</string>\n    <string name=\"delete\">删除</string>\n\n    <string name=\"del_file_error_empty\">已经为空文件夹</string>\n    <string name=\"del_cache_files\">删除所有已下载文件</string>\n\n\n    <string name=\"single_task_test_title\">单任务测试</string>\n    <string name=\"multitask_test_title\">多任务测试</string>\n    <string name=\"performance_test_title\">基础性能测试</string>\n    <string name=\"performance_test_parcel\">100000(times) 基础性能测试</string>\n    <string name=\"performance_test_output\">写文件性能测试</string>\n\n    <string name=\"tasks_manager_demo_title\">任务管理器案例</string>\n    <string name=\"tasks_manager_demo_name\" formatted=\"false\">任务: %d</string>\n    <string name=\"tasks_manager_demo_status_loading\">状态: 加载中...</string>\n    <string name=\"tasks_manager_demo_status_pending\">状态: 队列中</string>\n    <string name=\"tasks_manager_demo_status_started\">状态: 开始下载</string>\n    <string name=\"tasks_manager_demo_status_connected\">状态: 已连接上</string>\n    <string name=\"tasks_manager_demo_status_progress\">状态: 下载中...</string>\n    <string name=\"tasks_manager_demo_status_downloading\" formatted=\"false\">状态: 正在下载 %d</string>\n    <string name=\"tasks_manager_demo_status_error\">状态: 出错</string>\n    <string name=\"tasks_manager_demo_status_paused\">状态: 暂停</string>\n    <string name=\"tasks_manager_demo_status_completed\">状态: 下载完成</string>\n    <string name=\"tasks_manager_demo_status_not_downloaded\">状态: 未下载</string>\n\n    <string name=\"hybrid_test_title\">混合测试</string>\n    <string name=\"hybrid_test_deleted_file\" formatted=\"false\">已删除: %s</string>\n    <string name=\"hybrid_test_start_single_task\" formatted=\"false\">启动单任务下载: %s</string>\n    <string name=\"hybrid_test_start_multiple_tasks_parallel\" formatted=\"false\">启动%d个任务并行下载</string>\n    <string name=\"hybrid_test_start_multiple_tasks_serial\" formatted=\"false\">启动%d个任务串行下载</string>\n    <string name=\"hybrid_test_multiple_tasks_parallel_title\">多任务队列并行下载</string>\n    <string name=\"hybrid_test_multiple_tasks_serial_title\">多任务队列串行下载</string>\n    <string name=\"hybrid_test_single_task_title\">单任务下载测试</string>\n    <string name=\"avoid_missing_screen_frames\">避免掉帧</string>\n\n\n    <string name=\"notification_demo_title\">下载通知案例</string>\n\n    <string name=\"notification_sample_title\">下载通知简单案例</string>\n    <string name=\"notification_sample_show\">显示通知</string>\n\n\n    <string name=\"notification_min_set_title\">下载通知最简单案例</string>\n    <string name=\"notification_min_set_desc\">这是一个最少代码的Notification案例,在这里我们使用尽量少的代码将下载任务与通知绑定在一起。</string>\n    <string name=\"notification_sample_desc\">这是一个简单的 Notification 案例，每一个下载的任务都会有一个通知展示下载进度，且在下载结束后通知栏不会自动消失。同时，在 Actitiy 里有个动态的进度条表示整个的下载过程。</string>\n</resources>\n"
  },
  {
    "path": "demo/src/main/res/xml/network_security_config.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<network-security-config>\n    <domain-config cleartextTrafficPermitted=\"true\">\n        <domain includeSubdomains=\"true\">cdn.llsapp.com</domain>\n        <domain includeSubdomains=\"true\">117.143.109.173</domain>\n        <domain includeSubdomains=\"true\">httpwatch.com</domain>\n        <domain includeSubdomains=\"true\">mirror.internode.on.net</domain>\n        <domain includeSubdomains=\"true\">download.chinaunix.net</domain>\n        <domain includeSubdomains=\"true\">7xjww9.com1.z0.glb.clouddn.com</domain>\n        <domain includeSubdomains=\"true\">dg.101.hk</domain>\n        <domain includeSubdomains=\"true\">180.153.105.144</domain>\n        <domain includeSubdomains=\"true\">dlsw.baidu.com</domain>\n        <domain includeSubdomains=\"true\">pc6.com</domain>\n        <domain includeSubdomains=\"true\">113.207.16.84</domain>\n        <domain includeSubdomains=\"true\">down.tech.sina.com.cn</domain>\n        <domain includeSubdomains=\"true\">girlatlas.b0.upaiyun.com</domain>\n        <domain includeSubdomains=\"true\">cdn-l.llsapp.com</domain>\n        <domain includeSubdomains=\"true\">imgsrc.baidu.com</domain>\n        <domain includeSubdomains=\"true\">tech.down.sina.com.cn</domain>\n        <domain includeSubdomains=\"true\">placekitten.com</domain>\n    </domain-config>\n</network-security-config>\n"
  },
  {
    "path": "gradle/bintray.gradle",
    "content": "/*\n * Copyright (c) 2018 LingoChamp Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n\napply plugin: 'com.jfrog.bintray'\n\nafterEvaluate { project ->\n    task sourcesJar(type: Jar) {\n        from android.sourceSets.main.java.srcDirs\n        classifier = 'sources'\n    }\n\n    task javadoc(type: Javadoc) {\n        failOnError false\n        source = android.sourceSets.main.java.srcDirs\n        options {\n            charSet = 'UTF-8'\n            links \"http://docs.oracle.com/javase/7/docs/api/\"\n            linksOffline \"http://d.android.com/reference\", System.getenv(\"ANDROID_HOME\") + \"/docs/reference\"\n        }\n        classpath += project.android.libraryVariants.toList().first().javaCompile.classpath\n        classpath += project.files(android.getBootClasspath().join(File.pathSeparator))\n    }\n\n    task javadocJar(type: Jar, dependsOn: javadoc) {\n        classifier = 'javadoc'\n        from javadoc.destinationDir\n    }\n\n    artifacts {\n        archives javadocJar\n        archives sourcesJar\n    }\n}\n\n\ndef getBintrayUser() {\n    return hasProperty('BINTRAY_USER') ? BINTRAY_USER : \"\"\n}\n\ndef getBintrayGPGPassword() {\n    return hasProperty('BINTRAY_GPG_PASSWORD') ? BINTRAY_GPG_PASSWORD : \"\"\n}\n\ndef getBintrayApiKey() {\n    return hasProperty('BINTRAY_API_KEY') ? BINTRAY_API_KEY : \"\"\n}\n\ndef getSonatypeUsername() {\n    return hasProperty('SONATYPE_NEXUS_USERNAME') ? SONATYPE_NEXUS_USERNAME : \"\"\n}\n\ndef getSonatypePassword() {\n    return hasProperty('SONATYPE_NEXUS_PASSWORD') ? SONATYPE_NEXUS_PASSWORD : \"\"\n}\nversion = VERSION_NAME\n\nbintray {\n    user = getBintrayUser()\n    key = getBintrayApiKey()\n\n//    publish = true //[Default: false] Whether version should be auto published after an upload\n//    override = false //[Default: false] Whether to override version artifacts already published\n\n    configurations = ['archives']\n    pkg {\n        repo = \"maven\"\n        name = POM_NAME\n        desc = POM_DESCRIPTION\n        websiteUrl = POM_URL\n        issueTrackerUrl = ISSUE_URL\n        vcsUrl = POM_SCM_URL\n        licenses = [\"Apache-2.0\"]\n        publish = true\n        publicDownloadNumbers = true\n\n        githubRepo = POM_URL\n        githubReleaseNotesFile = 'README.md'\n\n        version {\n            gpg {\n                sign = true //Determines whether to GPG sign the files. The default is false\n                passphrase = getBintrayGPGPassword()\n            }\n\n            mavenCentralSync {\n                sync = true\n                user = getSonatypeUsername()\n                password = getSonatypePassword()\n                close = '1'  //Optional property. By default the staging repository is closed and artifacts are released to Maven Central. You can optionally turn this behaviour off (by puting 0 as value) and release the version manually.\n            }\n        }\n    }\n}"
  },
  {
    "path": "gradle/mvn-local.gradle",
    "content": "/*\n * Copyright (c) 2018 LingoChamp Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\napply plugin: 'maven-publish'\n\ntask sourceJar(type: Jar) {\n    from android.sourceSets.main.java.srcDirs\n    classifier \"source\"\n}\n\npublishing {\n    publications {\n        okdownload(MavenPublication) {\n            groupId GROUP\n            artifactId POM_ARTIFACT_ID\n            version VERSION_NAME\n            artifact(sourceJar)\n            artifact(\"$buildDir/outputs/aar/${project.name}-release.aar\")\n\n            pom.withXml {\n                def dependenciesNode = asNode().appendNode('dependencies')\n                configurations.compile.allDependencies.each {\n                    if (it.group != null\n                            && (it.name != null || \"unspecified\" == it.name)\n                            && it.version != null) {\n                        def dependencyNode = dependenciesNode.appendNode('dependency')\n                        dependencyNode.appendNode('groupId', it.group)\n                        dependencyNode.appendNode('artifactId', it.name)\n                        dependencyNode.appendNode('version', it.version)\n                    }\n                }\n            }\n        }\n    }\n}"
  },
  {
    "path": "gradle/mvn-push.gradle",
    "content": "/*\n * Copyright 2013 Chris Banes\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\napply plugin: 'maven'\napply plugin: 'signing'\napply from: '../gradle/mvn-local.gradle'\napply from: '../gradle/bintray.gradle'\n\nversion = VERSION_NAME\ngroup = GROUP\n\ndef isReleaseBuild() {\n  return VERSION_NAME.contains(\"SNAPSHOT\") == false\n}\n\ndef getReleaseRepositoryUrl() {\n  return hasProperty('RELEASE_REPOSITORY_URL') ? RELEASE_REPOSITORY_URL :\n      \"https://oss.sonatype.org/service/local/staging/deploy/maven2/\"\n}\n\ndef getSnapshotRepositoryUrl() {\n  return hasProperty('SNAPSHOT_REPOSITORY_URL') ? SNAPSHOT_REPOSITORY_URL :\n      \"https://oss.sonatype.org/content/repositories/snapshots/\"\n}\n\ndef getRepositoryUsername() {\n  return hasProperty('SONATYPE_NEXUS_USERNAME') ? SONATYPE_NEXUS_USERNAME : \"\"\n}\n\ndef getRepositoryPassword() {\n  return hasProperty('SONATYPE_NEXUS_PASSWORD') ? SONATYPE_NEXUS_PASSWORD : \"\"\n}\n\nafterEvaluate { project ->\n  uploadArchives {\n    repositories {\n      mavenDeployer {\n        beforeDeployment { MavenDeployment deployment -> signing.signPom(deployment) }\n\n        pom.groupId = GROUP\n        pom.artifactId = POM_ARTIFACT_ID\n        pom.version = VERSION_NAME\n\n        repository(url: getReleaseRepositoryUrl()) {\n          authentication(userName: getRepositoryUsername(), password: getRepositoryPassword())\n        }\n        snapshotRepository(url: getSnapshotRepositoryUrl()) {\n          authentication(userName: getRepositoryUsername(), password: getRepositoryPassword())\n        }\n\n        pom.project {\n          name POM_NAME\n          packaging POM_PACKAGING\n          description POM_DESCRIPTION\n          url POM_URL\n\n          scm {\n            url POM_SCM_URL\n            connection POM_SCM_CONNECTION\n            developerConnection POM_SCM_DEV_CONNECTION\n          }\n\n          licenses {\n            license {\n              name POM_LICENCE_NAME\n              url POM_LICENCE_URL\n              distribution POM_LICENCE_DIST\n            }\n          }\n\n          developers {\n            developer {\n              id POM_DEVELOPER_ID\n              name POM_DEVELOPER_NAME\n            }\n          }\n        }\n      }\n    }\n  }\n\n  signing {\n    required { isReleaseBuild() && gradle.taskGraph.hasTask(\"uploadArchives\") }\n    sign configurations.archives\n  }\n\n  if (project.getPlugins().hasPlugin('com.android.application') ||\n      project.getPlugins().hasPlugin('com.android.library')) {\n    task install(type: Upload, dependsOn: assemble) {\n      repositories.mavenInstaller {\n        configuration = configurations.archives\n\n        pom.groupId = GROUP\n        pom.artifactId = POM_ARTIFACT_ID\n        pom.version = VERSION_NAME\n\n        pom.project {\n          name POM_NAME\n          packaging POM_PACKAGING\n          description POM_DESCRIPTION\n          url POM_URL\n\n          scm {\n            url POM_SCM_URL\n            connection POM_SCM_CONNECTION\n            developerConnection POM_SCM_DEV_CONNECTION\n          }\n\n          licenses {\n            license {\n              name POM_LICENCE_NAME\n              url POM_LICENCE_URL\n              distribution POM_LICENCE_DIST\n            }\n          }\n\n          developers {\n            developer {\n              id POM_DEVELOPER_ID\n              name POM_DEVELOPER_NAME\n            }\n          }\n        }\n      }\n    }\n\n    task androidJavadocs(type: Javadoc) {\n      source = android.sourceSets.main.java.source\n      classpath += project.files(android.getBootClasspath().join(File.pathSeparator))\n    }\n\n    task androidJavadocsJar(type: Jar, dependsOn: androidJavadocs) {\n      classifier = 'javadoc'\n      from androidJavadocs.destinationDir\n    }\n\n    task androidSourcesJar(type: Jar) {\n      classifier = 'sources'\n      from android.sourceSets.main.java.source\n    }\n  } else {\n    install {\n      repositories.mavenInstaller {\n        pom.groupId = GROUP\n        pom.artifactId = POM_ARTIFACT_ID\n        pom.version = VERSION_NAME\n\n        pom.project {\n          name POM_NAME\n          packaging POM_PACKAGING\n          description POM_DESCRIPTION\n          url POM_URL\n\n          scm {\n            url POM_SCM_URL\n            connection POM_SCM_CONNECTION\n            developerConnection POM_SCM_DEV_CONNECTION\n          }\n\n          licenses {\n            license {\n              name POM_LICENCE_NAME\n              url POM_LICENCE_URL\n              distribution POM_LICENCE_DIST\n            }\n          }\n\n          developers {\n            developer {\n              id POM_DEVELOPER_ID\n              name POM_DEVELOPER_NAME\n            }\n          }\n        }\n      }\n    }\n\n    task sourcesJar(type: Jar, dependsOn: classes) {\n      classifier = 'sources'\n      from sourceSets.main.allSource\n    }\n\n    task javadocJar(type: Jar, dependsOn: javadoc) {\n      classifier = 'javadoc'\n      from javadoc.destinationDir\n    }\n  }\n\n  if (JavaVersion.current().isJava8Compatible()) {\n    allprojects {\n      tasks.withType(Javadoc) {\n        options.addStringOption('Xdoclint:none', '-quiet')\n      }\n    }\n  }\n\n  artifacts {\n    if (project.getPlugins().hasPlugin('com.android.application') ||\n        project.getPlugins().hasPlugin('com.android.library')) {\n      archives androidSourcesJar\n      archives androidJavadocsJar\n    } else {\n      archives sourcesJar\n      archives javadocJar\n    }\n  }\n}\n"
  },
  {
    "path": "gradle/wrapper/gradle-wrapper.properties",
    "content": "#Wed Feb 20 10:49:17 CST 2019\ndistributionBase=GRADLE_USER_HOME\ndistributionPath=wrapper/dists\nzipStoreBase=GRADLE_USER_HOME\nzipStorePath=wrapper/dists\ndistributionUrl=https\\://services.gradle.org/distributions/gradle-4.10.1-all.zip\n"
  },
  {
    "path": "gradle.properties",
    "content": "VERSION_NAME=1.7.8-SNAPSHOT\nBUILD_TOOLS_VERSION=28.0.3\nCOMPILE_SDK_VERSION=28\n\nGROUP=com.liulishuo.filedownloader\n\nPOM_URL=https://github.com/lingochamp/FileDownloader\nISSUE_URL=https://github.com/lingochamp/FileDownloader/issues\n\nPOM_SCM_URL=https://github.com/lingochamp/FileDownloader\nPOM_SCM_CONNECTION=scm:git@github.com:lingochamp/FileDownloader.git\nPOM_SCM_DEV_CONNECTION=scm:git@github.com:lingochamp/FileDownloader.git\n\nPOM_LICENCE_NAME=The Apache Software License, Version 2.0\nPOM_LICENCE_URL=http://www.apache.org/licenses/LICENSE-2.0.txt\nPOM_LICENCE_DIST=repo\n\nPOM_DEVELOPER_ID=lingochamp\nPOM_DEVELOPER_NAME=LingoChamp Inc.\n"
  },
  {
    "path": "gradlew",
    "content": "#!/usr/bin/env bash\n\n##############################################################################\n##\n##  Gradle start up script for UN*X\n##\n##############################################################################\n\n# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.\nDEFAULT_JVM_OPTS=\"\"\n\nAPP_NAME=\"Gradle\"\nAPP_BASE_NAME=`basename \"$0\"`\n\n# Use the maximum available, or set MAX_FD != -1 to use that value.\nMAX_FD=\"maximum\"\n\nwarn ( ) {\n    echo \"$*\"\n}\n\ndie ( ) {\n    echo\n    echo \"$*\"\n    echo\n    exit 1\n}\n\n# OS specific support (must be 'true' or 'false').\ncygwin=false\nmsys=false\ndarwin=false\ncase \"`uname`\" in\n  CYGWIN* )\n    cygwin=true\n    ;;\n  Darwin* )\n    darwin=true\n    ;;\n  MINGW* )\n    msys=true\n    ;;\nesac\n\n# Attempt to set APP_HOME\n# Resolve links: $0 may be a link\nPRG=\"$0\"\n# Need this for relative symlinks.\nwhile [ -h \"$PRG\" ] ; do\n    ls=`ls -ld \"$PRG\"`\n    link=`expr \"$ls\" : '.*-> \\(.*\\)$'`\n    if expr \"$link\" : '/.*' > /dev/null; then\n        PRG=\"$link\"\n    else\n        PRG=`dirname \"$PRG\"`\"/$link\"\n    fi\ndone\nSAVED=\"`pwd`\"\ncd \"`dirname \\\"$PRG\\\"`/\" >/dev/null\nAPP_HOME=\"`pwd -P`\"\ncd \"$SAVED\" >/dev/null\n\nCLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar\n\n# Determine the Java command to use to start the JVM.\nif [ -n \"$JAVA_HOME\" ] ; then\n    if [ -x \"$JAVA_HOME/jre/sh/java\" ] ; then\n        # IBM's JDK on AIX uses strange locations for the executables\n        JAVACMD=\"$JAVA_HOME/jre/sh/java\"\n    else\n        JAVACMD=\"$JAVA_HOME/bin/java\"\n    fi\n    if [ ! -x \"$JAVACMD\" ] ; then\n        die \"ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME\n\nPlease set the JAVA_HOME variable in your environment to match the\nlocation of your Java installation.\"\n    fi\nelse\n    JAVACMD=\"java\"\n    which java >/dev/null 2>&1 || die \"ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.\n\nPlease set the JAVA_HOME variable in your environment to match the\nlocation of your Java installation.\"\nfi\n\n# Increase the maximum file descriptors if we can.\nif [ \"$cygwin\" = \"false\" -a \"$darwin\" = \"false\" ] ; then\n    MAX_FD_LIMIT=`ulimit -H -n`\n    if [ $? -eq 0 ] ; then\n        if [ \"$MAX_FD\" = \"maximum\" -o \"$MAX_FD\" = \"max\" ] ; then\n            MAX_FD=\"$MAX_FD_LIMIT\"\n        fi\n        ulimit -n $MAX_FD\n        if [ $? -ne 0 ] ; then\n            warn \"Could not set maximum file descriptor limit: $MAX_FD\"\n        fi\n    else\n        warn \"Could not query maximum file descriptor limit: $MAX_FD_LIMIT\"\n    fi\nfi\n\n# For Darwin, add options to specify how the application appears in the dock\nif $darwin; then\n    GRADLE_OPTS=\"$GRADLE_OPTS \\\"-Xdock:name=$APP_NAME\\\" \\\"-Xdock:icon=$APP_HOME/media/gradle.icns\\\"\"\nfi\n\n# For Cygwin, switch paths to Windows format before running java\nif $cygwin ; then\n    APP_HOME=`cygpath --path --mixed \"$APP_HOME\"`\n    CLASSPATH=`cygpath --path --mixed \"$CLASSPATH\"`\n    JAVACMD=`cygpath --unix \"$JAVACMD\"`\n\n    # We build the pattern for arguments to be converted via cygpath\n    ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`\n    SEP=\"\"\n    for dir in $ROOTDIRSRAW ; do\n        ROOTDIRS=\"$ROOTDIRS$SEP$dir\"\n        SEP=\"|\"\n    done\n    OURCYGPATTERN=\"(^($ROOTDIRS))\"\n    # Add a user-defined pattern to the cygpath arguments\n    if [ \"$GRADLE_CYGPATTERN\" != \"\" ] ; then\n        OURCYGPATTERN=\"$OURCYGPATTERN|($GRADLE_CYGPATTERN)\"\n    fi\n    # Now convert the arguments - kludge to limit ourselves to /bin/sh\n    i=0\n    for arg in \"$@\" ; do\n        CHECK=`echo \"$arg\"|egrep -c \"$OURCYGPATTERN\" -`\n        CHECK2=`echo \"$arg\"|egrep -c \"^-\"`                                 ### Determine if an option\n\n        if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then                    ### Added a condition\n            eval `echo args$i`=`cygpath --path --ignore --mixed \"$arg\"`\n        else\n            eval `echo args$i`=\"\\\"$arg\\\"\"\n        fi\n        i=$((i+1))\n    done\n    case $i in\n        (0) set -- ;;\n        (1) set -- \"$args0\" ;;\n        (2) set -- \"$args0\" \"$args1\" ;;\n        (3) set -- \"$args0\" \"$args1\" \"$args2\" ;;\n        (4) set -- \"$args0\" \"$args1\" \"$args2\" \"$args3\" ;;\n        (5) set -- \"$args0\" \"$args1\" \"$args2\" \"$args3\" \"$args4\" ;;\n        (6) set -- \"$args0\" \"$args1\" \"$args2\" \"$args3\" \"$args4\" \"$args5\" ;;\n        (7) set -- \"$args0\" \"$args1\" \"$args2\" \"$args3\" \"$args4\" \"$args5\" \"$args6\" ;;\n        (8) set -- \"$args0\" \"$args1\" \"$args2\" \"$args3\" \"$args4\" \"$args5\" \"$args6\" \"$args7\" ;;\n        (9) set -- \"$args0\" \"$args1\" \"$args2\" \"$args3\" \"$args4\" \"$args5\" \"$args6\" \"$args7\" \"$args8\" ;;\n    esac\nfi\n\n# Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules\nfunction splitJvmOpts() {\n    JVM_OPTS=(\"$@\")\n}\neval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS\nJVM_OPTS[${#JVM_OPTS[*]}]=\"-Dorg.gradle.appname=$APP_BASE_NAME\"\n\nexec \"$JAVACMD\" \"${JVM_OPTS[@]}\" -classpath \"$CLASSPATH\" org.gradle.wrapper.GradleWrapperMain \"$@\"\n"
  },
  {
    "path": "gradlew.bat",
    "content": "@if \"%DEBUG%\" == \"\" @echo off\r\n@rem ##########################################################################\r\n@rem\r\n@rem  Gradle startup script for Windows\r\n@rem\r\n@rem ##########################################################################\r\n\r\n@rem Set local scope for the variables with windows NT shell\r\nif \"%OS%\"==\"Windows_NT\" setlocal\r\n\r\n@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.\r\nset DEFAULT_JVM_OPTS=\r\n\r\nset DIRNAME=%~dp0\r\nif \"%DIRNAME%\" == \"\" set DIRNAME=.\r\nset APP_BASE_NAME=%~n0\r\nset APP_HOME=%DIRNAME%\r\n\r\n@rem Find java.exe\r\nif defined JAVA_HOME goto findJavaFromJavaHome\r\n\r\nset JAVA_EXE=java.exe\r\n%JAVA_EXE% -version >NUL 2>&1\r\nif \"%ERRORLEVEL%\" == \"0\" goto init\r\n\r\necho.\r\necho ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.\r\necho.\r\necho Please set the JAVA_HOME variable in your environment to match the\r\necho location of your Java installation.\r\n\r\ngoto fail\r\n\r\n:findJavaFromJavaHome\r\nset JAVA_HOME=%JAVA_HOME:\"=%\r\nset JAVA_EXE=%JAVA_HOME%/bin/java.exe\r\n\r\nif exist \"%JAVA_EXE%\" goto init\r\n\r\necho.\r\necho ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%\r\necho.\r\necho Please set the JAVA_HOME variable in your environment to match the\r\necho location of your Java installation.\r\n\r\ngoto fail\r\n\r\n:init\r\n@rem Get command-line arguments, handling Windowz variants\r\n\r\nif not \"%OS%\" == \"Windows_NT\" goto win9xME_args\r\nif \"%@eval[2+2]\" == \"4\" goto 4NT_args\r\n\r\n:win9xME_args\r\n@rem Slurp the command line arguments.\r\nset CMD_LINE_ARGS=\r\nset _SKIP=2\r\n\r\n:win9xME_args_slurp\r\nif \"x%~1\" == \"x\" goto execute\r\n\r\nset CMD_LINE_ARGS=%*\r\ngoto execute\r\n\r\n:4NT_args\r\n@rem Get arguments from the 4NT Shell from JP Software\r\nset CMD_LINE_ARGS=%$\r\n\r\n:execute\r\n@rem Setup the command line\r\n\r\nset CLASSPATH=%APP_HOME%\\gradle\\wrapper\\gradle-wrapper.jar\r\n\r\n@rem Execute Gradle\r\n\"%JAVA_EXE%\" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% \"-Dorg.gradle.appname=%APP_BASE_NAME%\" -classpath \"%CLASSPATH%\" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%\r\n\r\n:end\r\n@rem End local scope for the variables with windows NT shell\r\nif \"%ERRORLEVEL%\"==\"0\" goto mainEnd\r\n\r\n:fail\r\nrem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of\r\nrem the _cmd.exe /c_ return code!\r\nif  not \"\" == \"%GRADLE_EXIT_CONSOLE%\" exit 1\r\nexit /b 1\r\n\r\n:mainEnd\r\nif \"%OS%\"==\"Windows_NT\" endlocal\r\n\r\n:omega\r\n"
  },
  {
    "path": "install.sh",
    "content": "#!/bin/bash\n\nadb install -r demo/build/outputs/apk/demo-debug.apk\n\nif [ \"$1\" == \"y\" ]; then\n\tadb shell am start -n \"com.liulishuo.filedownloader.demo/com.liulishuo.filedownloader.demo.MainActivity\" -a android.intent.action.MAIN -c android.intent.category.LAUNCHER\nfi\n"
  },
  {
    "path": "library/.gitignore",
    "content": "/build\n"
  },
  {
    "path": "library/LICENSE",
    "content": "Copyright (c) 2015 LingoChamp Inc.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n   http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n"
  },
  {
    "path": "library/build.gradle",
    "content": "apply plugin: 'com.android.library'\n\nandroid {\n    compileSdkVersion COMPILE_SDK_VERSION as int\n    buildToolsVersion BUILD_TOOLS_VERSION as String\n\n    defaultConfig {\n        minSdkVersion 9\n        versionName VERSION_NAME as String\n    }\n}\n\n\nplugins.apply('checkstyle')\n\ntask('checkstyle', type: Checkstyle) {\n    configFile rootProject.file('checkstyle.xml')\n    source 'src/main/java'\n    source 'src/test/java'\n    ignoreFailures false\n    showViolations true\n    include '**/*.java'\n\n    classpath = files()\n}\n\nafterEvaluate {\n    tasks.findByName('check').dependsOn('checkstyle')\n}\n\ncheckstyle {\n    toolVersion = '6.18'\n}\n\ndependencies {\n    testImplementation \"junit:junit:4.12\"\n    //noinspection GradleDynamicVersion\n    testImplementation \"org.mockito:mockito-core:2.+\"\n    testImplementation \"org.robolectric:robolectric:3.3.2\"\n}\n\napply from: rootProject.file('gradle/mvn-push.gradle')\n"
  },
  {
    "path": "library/gradle.properties",
    "content": "POM_ARTIFACT_ID=library\nPOM_NAME=FileDownloader\nPOM_DESCRIPTION=Android multi-task file download engine.\nPOM_PACKAGING=aar\n"
  },
  {
    "path": "library/proguard-rules.pro",
    "content": "# Add project specific ProGuard rules here.\n# By default, the flags in this file are appended to flags specified\n# in /Users/Jacksgong/Develop/sdk/android-sdk/tools/proguard/proguard-android.txt\n# You can edit the include path and order by changing the proguardFiles\n# directive in build.gradle.\n#\n# For more details, see\n#   http://developer.android.com/guide/developing/tools/proguard.html\n\n# Add any project specific keep options here:\n\n# If your project uses WebView with JS, uncomment the following\n# and specify the fully qualified class name to the JavaScript interface\n# class:\n#-keepclassmembers class fqcn.of.javascript.interface.for.webview {\n#   public *;\n#}\n"
  },
  {
    "path": "library/src/main/AndroidManifest.xml",
    "content": "<manifest xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    package=\"com.liulishuo.filedownloader\">\n\n    <!-- To allow starting foreground services on Android P+ - https://developer.android.com/preview/behavior-changes#fg-svc -->\n    <uses-permission android:name=\"android.permission.FOREGROUND_SERVICE\" />\n\n    <application>\n        <service android:name=\".services.FileDownloadService$SharedMainProcessService\" />\n        <service\n            android:name=\".services.FileDownloadService$SeparateProcessService\"\n            android:process=\":filedownloader\" />\n    </application>\n\n</manifest>\n"
  },
  {
    "path": "library/src/main/aidl/com/liulishuo/filedownloader/i/IFileDownloadIPCCallback.aidl",
    "content": "package com.liulishuo.filedownloader.i;\n\nimport com.liulishuo.filedownloader.message.MessageSnapshot;\n\ninterface IFileDownloadIPCCallback {\n    oneway void callback(in MessageSnapshot snapshot);\n}\n"
  },
  {
    "path": "library/src/main/aidl/com/liulishuo/filedownloader/i/IFileDownloadIPCService.aidl",
    "content": "package com.liulishuo.filedownloader.i;\n\nimport com.liulishuo.filedownloader.i.IFileDownloadIPCCallback;\nimport com.liulishuo.filedownloader.message.MessageSnapshot;\nimport com.liulishuo.filedownloader.model.FileDownloadHeader;\nimport com.liulishuo.filedownloader.model.FileDownloadTaskAtom;\nimport android.app.Notification;\n\ninterface IFileDownloadIPCService {\n\n    oneway void registerCallback(in IFileDownloadIPCCallback callback);\n    oneway void unregisterCallback(in IFileDownloadIPCCallback callback);\n\n    boolean checkDownloading(String url, String path);\n    // why not use `oneway` to optimize the performance of the below `start` method? because if we\n    // use `oneway` it will be very hard to decide how is the binder thread going according to the context.\n    // and in this way(not is `oneway`), we can block the download before its launch only\n    // by {@link FileDownloadEventPool#shutdownSendPool} according to the context, because it\n    // will execute sync on the {@link FileDownloadEventPool#sendPool}\n    void start(String url, String path, boolean pathAsDirectory, int callbackProgressTimes,\n                int callbackProgressMinIntervalMillis, int autoRetryTimes, boolean forceReDownload,\n                in FileDownloadHeader header, boolean isWifiRequired);\n    boolean pause(int downloadId);\n    void pauseAllTasks();\n\n    boolean setMaxNetworkThreadCount(int count);\n\n    long getSofar(int downloadId);\n    long getTotal(int downloadId);\n    byte getStatus(int downloadId);\n    boolean isIdle();\n\n    oneway void startForeground(int id, in Notification notification);\n    oneway void stopForeground(boolean removeNotification);\n\n    boolean clearTaskData(int id);\n\n    void clearAllTaskData();\n}\n"
  },
  {
    "path": "library/src/main/aidl/com/liulishuo/filedownloader/message/MessageSnapshot.aidl",
    "content": "package com.liulishuo.filedownloader.message;\n\nparcelable MessageSnapshot;"
  },
  {
    "path": "library/src/main/aidl/com/liulishuo/filedownloader/model/FileDownloadHeader.aidl",
    "content": "package com.liulishuo.filedownloader.model;\n\nparcelable FileDownloadHeader;"
  },
  {
    "path": "library/src/main/aidl/com/liulishuo/filedownloader/model/FileDownloadTaskAtom.aidl",
    "content": "package com.liulishuo.filedownloader.model;\n\nparcelable FileDownloadTaskAtom;"
  },
  {
    "path": "library/src/main/java/com/liulishuo/filedownloader/BaseDownloadTask.java",
    "content": "/*\n * Copyright (c) 2015 LingoChamp Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *    http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.liulishuo.filedownloader;\n\nimport com.liulishuo.filedownloader.exception.FileDownloadGiveUpRetryException;\nimport com.liulishuo.filedownloader.message.MessageSnapshotThreadPool;\nimport com.liulishuo.filedownloader.model.FileDownloadStatus;\nimport com.liulishuo.filedownloader.util.FileDownloadUtils;\n\n/**\n * An atom download task.\n *\n * @see FileDownloader\n * @see ITaskHunter\n */\n@SuppressWarnings({\"WeakerAccess\", \"UnusedReturnValue\"})\npublic interface BaseDownloadTask {\n\n    int DEFAULT_CALLBACK_PROGRESS_MIN_INTERVAL_MILLIS = 10;\n\n    /**\n     * @param minIntervalUpdateSpeedMs The minimum interval millisecond for updating the downloading\n     *                                 speed in downloading process(Status equal to progress).\n     *                                 <p>\n     *                                 Default: 5 ms.\n     *                                 <p>\n     *                                 If the value is less than or equal to 0, will not calculate\n     *                                 the download speed in process.\n     */\n    BaseDownloadTask setMinIntervalUpdateSpeed(int minIntervalUpdateSpeedMs);\n\n    /**\n     * @param path {@code path} = (absolute directory/filename); and {@link #isPathAsDirectory()}\n     *             assign to {@code false}.\n     */\n    BaseDownloadTask setPath(final String path);\n\n    /**\n     * @param path            The absolute path for saving the download file.\n     * @param pathAsDirectory {@code true}: if the {@code path} is absolute directory to store the\n     *                        downloading file, and the {@code filename} will be found in\n     *                        contentDisposition from the response as default, if can't find\n     *                        contentDisposition,the {@code filename} will be generated by\n     *                        {@link FileDownloadUtils#generateFileName(String)}  with {@code url}.\n     *                        </p>\n     *                        {@code false}: if the {@code path} = (absolute directory/filename).\n     * @see #isPathAsDirectory()\n     * @see #getFilename()\n     */\n    BaseDownloadTask setPath(final String path, final boolean pathAsDirectory);\n\n    /**\n     * @param listener For callback download status(pending,connected,progress,\n     *                 blockComplete,retry,error,paused,completed,warn)\n     */\n    BaseDownloadTask setListener(final FileDownloadListener listener);\n\n    /**\n     * Set the maximum callback count of\n     * {@link FileDownloadListener#progress(BaseDownloadTask, int, int)} during the entire process\n     * of downloading.\n     * <p>\n     * <strong>Note:</strong> this function will not work if the URL is refer to 'chucked' resource.\n     *\n     * @param callbackProgressCount The maximum callback count of\n     *                              {@link FileDownloadListener#progress}\n     *                              during the entire process of downloading.\n     *                              <p>\n     *                              Default value is 100, If the value less than or equal to 0, you\n     *                              will not receive any callback of\n     *                              {@link FileDownloadListener#progress}\n     *                              .\n     * @see #setCallbackProgressMinInterval(int)\n     */\n    BaseDownloadTask setCallbackProgressTimes(int callbackProgressCount);\n\n    /**\n     * Set the minimum time interval between each callback of\n     * {@link FileDownloadListener#progress(BaseDownloadTask, int, int)}.\n     *\n     * @param minIntervalMillis The minimum interval between each callback of\n     *                          {@link FileDownloadListener#progress(BaseDownloadTask, int, int)}.\n     *                          <p>\n     *                          Unit: millisecond.\n     *                          <p>\n     *                          Default value is\n     *                          {@link #DEFAULT_CALLBACK_PROGRESS_MIN_INTERVAL_MILLIS}.\n     *                          <p>\n     *                          Scope: [5, {@link Integer#MAX_VALUE}.\n     * @see #setCallbackProgressTimes(int)\n     */\n    BaseDownloadTask setCallbackProgressMinInterval(int minIntervalMillis);\n\n    /**\n     * Ignore all callbacks of {@link FileDownloadListener#progress(BaseDownloadTask, int, int)}\n     * during the entire process of downloading. This will optimize the performance.\n     */\n    BaseDownloadTask setCallbackProgressIgnored();\n\n    /**\n     * Sets the tag associated with this task, not be used by internal.\n     */\n    BaseDownloadTask setTag(final Object tag);\n\n    /**\n     * Set a tag associated with this task, not be used by internal.\n     *\n     * @param key The key of identifying the tag.\n     *            If the key already exists, the old data will be replaced.\n     * @param tag An Object to tag the task with\n     */\n    BaseDownloadTask setTag(final int key, final Object tag);\n\n\n    /**\n     * Force re-downloading the file regardless the target file is exist.\n     *\n     * @param isForceReDownload If set to true, will not check whether the target file is exist.\n     *                          <p>\n     *                          Default: {@code false}.\n     */\n    BaseDownloadTask setForceReDownload(final boolean isForceReDownload);\n\n    /**\n     * @deprecated Replace with {@link #addFinishListener(FinishListener)}\n     */\n    BaseDownloadTask setFinishListener(final FinishListener finishListener);\n\n    /**\n     * Add the finish listener to listen when the task is finished.\n     * <p>\n     * This listener's method {@link FinishListener#over(BaseDownloadTask)} will be invoked in\n     * Internal-Flow-Thread directly, which is controlled by {@link MessageSnapshotThreadPool}.\n     *\n     * @param finishListener The finish listener.\n     * @see FileDownloadStatus#isOver(int)\n     */\n    BaseDownloadTask addFinishListener(final FinishListener finishListener);\n\n    /**\n     * Remove the finish listener from this task.\n     *\n     * @param finishListener The finish listener.\n     * @return {@code true} if remove the {@code finishListener} successfully.\n     * {@code false} otherwise.\n     */\n    boolean removeFinishListener(final FinishListener finishListener);\n\n    /**\n     * Set the number of times to retry when encounter any error, except\n     * {@link com.liulishuo.filedownloader.exception.FileDownloadGiveUpRetryException}.\n     *\n     * @param autoRetryTimes The retry times, default 0.\n     */\n    BaseDownloadTask setAutoRetryTimes(int autoRetryTimes);\n\n    /**\n     * Add the params to the request header.\n     * <p>\n     * <strong>Note:</strong> We have already handled Etag internal for guaranteeing tasks resuming\n     * from the breakpoint, in other words, if the task has downloaded and got Etag, we will add the\n     * 'If-Match' and the 'Range' K-V to its request header automatically.\n     */\n    BaseDownloadTask addHeader(final String name, final String value);\n\n    /**\n     * Add a field with the specified value to the request header.\n     */\n    BaseDownloadTask addHeader(final String line);\n\n    /**\n     * Remove all fields in the request header.\n     */\n    BaseDownloadTask removeAllHeaders(final String name);\n\n    /**\n     * @param syncCallback {@code true} FileDownloader will invoke methods of\n     *                     {@link FileDownloadListener} directly on the download thread(isn't in the\n     *                     main thread).\n     */\n    BaseDownloadTask setSyncCallback(final boolean syncCallback);\n\n    /**\n     * Set whether this task only allows downloading on the wifi network type. Default {@code false}\n     * <p>\n     * <strong>Note:</strong> If {@code isWifiRequired} is {@code true}, FileDownloader will check\n     * the network type every time after fetch less than or equal to 4096 bytes data from the\n     * network, what will result in slowing the download speed slightly.\n     * <p>\n     * <strong>Permission:</strong> If {@code isWifiRequired} is {@code true}, You need declare the\n     * permission {@link android.Manifest.permission#ACCESS_NETWORK_STATE} in your manifest, let\n     * FileDownloader has permission to check the network type in downloading, if you start this\n     * task without this permission you will receive a {@link FileDownloadGiveUpRetryException}.\n     *\n     * @param isWifiRequired {@code true} This task only allow to download on the wifi network type.\n     */\n    BaseDownloadTask setWifiRequired(final boolean isWifiRequired);\n\n    /**\n     * Ready this task(For the task in a queue).\n     * <p>\n     * <strong>Note:</strong> If this task doesn't belong to a queue, what is just an isolated task,\n     * you just need to invoke {@link #start()} to start this task, that's all. In other words, If\n     * this task doesn't belong to a queue, you must not invoke this method or\n     * {@link InQueueTask#enqueue()} method before invoke {@link #start()}, If you do that and if\n     * there is the same listener object to start a queue in another thread, this task may be\n     * assembled by the queue, in that case, when you invoke {@link #start()} manually to start this\n     * task or this task is started by the queue, there is an exception buried in there, because\n     * this task object is started two times without declare {@link #reuse()} : 1. you invoke\n     * {@link #start()} manually; 2. the queue start this task automatically.\n     *\n     * @return downloadId the download identify.\n     * @see FileDownloader#start(FileDownloadListener, boolean)\n     * @deprecated please use {@link #asInQueueTask()} first and when you need to enqueue this task\n     * to the global queue to make this task is ready to be assembled by the queue which makes up of\n     * the same listener task, just invoke {@link InQueueTask#enqueue()}.\n     */\n    int ready();\n\n    /**\n     * Declare the task will be assembled by a queue which makes up of the same listener task.\n     * <p>\n     * <strong>Note:</strong> If you use {@link FileDownloadQueueSet} to start this task in a queue,\n     * you don't need to invoke this method manually, it has been handled by\n     * {@link FileDownloadQueueSet}.\n     *\n     * @return the task which is in a queue and exposes method {@link InQueueTask#enqueue()} to\n     * enqueue this task to the global queue to ready for being assembled by the queue.\n     */\n    InQueueTask asInQueueTask();\n\n    /**\n     * Reuse this task withhold request params: path、url、header、isForceReDownloader、etc.\n     * <p>\n     * <strong>Note:</strong>If the task has been over({@link FileDownloadStatus#isOver(int)}), but\n     * the over-message has not been handover to the listener, since the callback is asynchronous,\n     * once your invoke this 'reuse' method, that message would be discard, for free the messenger.\n     *\n     * @return {@code true} if reuse this task successfully. {@code false} otherwise.\n     */\n    boolean reuse();\n\n\n    /**\n     * @return {@code true} if the this task already has downloading data, it means that this task\n     * is running or has ran.\n     * @see #isRunning()\n     * @see #start()\n     * @see #reuse()\n     */\n    boolean isUsing();\n\n    /**\n     * @return {@code true} if this task is running, in this case, this task isn't allow\n     * to {@link #start()} again for this task object, and even not allow to {@link #reuse()}.\n     * {@code false} this task maybe {@link #isUsing()} or in idle.\n     * @see #isUsing()\n     * @see #start()\n     */\n    boolean isRunning();\n\n    /**\n     * Whether this task has already attached to a listener / a serial-queue.\n     *\n     * @return {@code true} if this task is running, and it has already attached to the listener or\n     * has already assembled to a serial-queue and would be started automatically when it is come to\n     * its turn.\n     * @see IQueuesHandler#startQueueSerial(FileDownloadListener)\n     * @see IQueuesHandler#startQueueParallel(FileDownloadListener)\n     */\n    @SuppressWarnings(\"BooleanMethodIsAlwaysInverted\")\n    boolean isAttached();\n\n    /**\n     * Start this task in parallel.\n     *\n     * @return The download id.\n     */\n    int start();\n\n    // -------------- Another Operations ---------------------\n\n    /**\n     * Why pause? not stop/cancel? because invoke this method(pause) will clear all data about this\n     * task in memory, and stop the total processing about this task. but when you start the paused\n     * task, it would be continue downloading from the breakpoint as default.\n     *\n     * @return {@code true}, if pause this task successfully, {@code false} otherwise this task has\n     * already in over status before invoke this method(this case maybe occurred in high concurrent\n     * situation).\n     * @see FileDownloader#pause(int)\n     * @see FileDownloader#pause(FileDownloadListener)\n     * @see FileDownloader#pauseAll()\n     */\n    boolean pause();\n\n    /**\n     * The {@link #pause()} also clear all data relate with this task in the memory, so please use\n     * {@link #pause()} instead.\n     *\n     * @return {@code true} if cancel this task successfully.\n     * @deprecated replace with {@link #pause()}\n     */\n    boolean cancel();\n    // ------------------- get -----------------------\n\n    /**\n     * The downloading identify of this task, what is generated by {@link #getUrl()} and\n     * {@link #getPath()} and {@link #isPathAsDirectory()} from\n     * {@link FileDownloadUtils#generateId(String, String)}.\n     *\n     * @return The downloading identify of this task.\n     * @see FileDownloader#pause(int)\n     * @see FileDownloader#getStatus(String, String)\n     * @see FileDownloader#getTotal(int)\n     * @see FileDownloader#getSoFar(int)\n     */\n    int getId();\n\n    /**\n     * @return The downloading identify of this task.\n     * @deprecated Used {@link #getId()} instead.\n     */\n    int getDownloadId();\n\n    /**\n     * @return The Url.\n     */\n    String getUrl();\n\n    /**\n     * @return The maximum callback count of\n     * {@link FileDownloadListener#progress(BaseDownloadTask, int, int)} during the entire process\n     * of downloading.\n     */\n    int getCallbackProgressTimes();\n\n    /**\n     * @return The minimum time interval between each callback of\n     * {@link FileDownloadListener#progress(BaseDownloadTask, int, int)} .\n     */\n    int getCallbackProgressMinInterval();\n\n    /**\n     * @return If {@link #isPathAsDirectory()} is {@code true}: {@code path} is a absolute directory\n     * to store the downloading file, and the {@code filename} will be found in contentDisposition\n     * from the response as default, if can't find contentDisposition, the {@code filename} will be\n     * generated by {@link FileDownloadUtils#generateFileName(String)}  with {@code url}. otherwise\n     * {@code path} is the absolute path of the target file.\n     */\n    String getPath();\n\n    /**\n     * @return Whether the result of {@link #getPath()} is a directory.\n     * @see #getPath()\n     */\n    boolean isPathAsDirectory();\n\n    /**\n     * @return If {@link #isPathAsDirectory()} is {@code true}, the {@code filename} will be found\n     * in contentDisposition from the response as default, if can't find contentDisposition, the\n     * {@code filename} will be generated by {@link FileDownloadUtils#generateFileName(String)} with\n     * {@code url}. It will be found before the callback of\n     * {@link FileDownloadListener#connected(BaseDownloadTask, String, boolean, int, int)}.\n     * </p>\n     * If {@link #isPathAsDirectory()} is {@code false}, the {@code filename} will be found\n     * immediately when you invoke {@link #setPath(String, boolean)} .\n     */\n    String getFilename();\n\n    /**\n     * @return The target file path to store the file.\n     */\n    String getTargetFilePath();\n\n    /**\n     * @return the downloading listener.\n     */\n    FileDownloadListener getListener();\n\n    /**\n     * @return The has already downloaded bytes so far.\n     * @deprecated replace with {@link #getSmallFileSoFarBytes()}.\n     */\n    int getSoFarBytes();\n\n    /**\n     * This method will be used when the length of target file is less than or equal to 1.99G.\n     *\n     * @return The has already downloaded bytes so far.\n     */\n    int getSmallFileSoFarBytes();\n\n    /**\n     * This method will be used when the length of target file is more than 1.99G.\n     *\n     * @return The has already downloaded bytes so far.\n     */\n    long getLargeFileSoFarBytes();\n\n    /**\n     * @return The total bytes of the target file.\n     * <p>\n     * <strong>Note:</strong> this value will be valid\n     * after {@link FileDownloadListener#connected(BaseDownloadTask, String, boolean, int, int)} or\n     * it has already have in the database.\n     * @deprecated replace with {@link #getSmallFileTotalBytes()}}\n     */\n    int getTotalBytes();\n\n    /**\n     * This method will be used when the length of target file is less than or equal to 1.99G.\n     *\n     * @return The total bytes of the target file.\n     */\n    int getSmallFileTotalBytes();\n\n    /**\n     * This method will be used when the length of target file is more than 1.99G.\n     *\n     * @return The total bytes of the target file.\n     */\n    long getLargeFileTotalBytes();\n\n    /**\n     * Get the downloading speed.\n     * <p>\n     * If the task is in the downloading process(status equal {@link FileDownloadStatus#progress}) :\n     * The value is a real-time speed. it is calculated when the interval from the last calculation\n     * more than {@link #setMinIntervalUpdateSpeed(int)} before each\n     * {@link FileDownloadListener#progress(BaseDownloadTask, int, int)} call-back method.\n     * <p/>\n     * If this task is finished({@link FileDownloadStatus#isOver(int)}): The value is a average\n     * speed. it is calculated from the entire downloading travel(connected, over).\n     *\n     * @return The downloading speed, Unit: KB/s.\n     * @see #setMinIntervalUpdateSpeed(int)\n     */\n    int getSpeed();\n\n    /**\n     * @return The downloading status.\n     * @see FileDownloadStatus\n     */\n    byte getStatus();\n\n    /**\n     * @return {@code true} if this task force re-download regard less the target file has already\n     * exist.\n     */\n    boolean isForceReDownload();\n\n    /**\n     * @deprecated Replaced with {@link #getErrorCause()}\n     */\n    Throwable getEx();\n\n    /**\n     * @return The error cause.\n     */\n    Throwable getErrorCause();\n\n\n    /**\n     * @return {@code true} if this task didn't start downloading really, because the target file\n     * has already exist. {@code false} otherwise.\n     * @see #isForceReDownload()\n     */\n    boolean isReusedOldFile();\n\n    /**\n     * @return The tag.\n     */\n    Object getTag();\n\n    /**\n     * Returns the tag associated with this task and the specified key.\n     *\n     * @param key The key identifying the tag.\n     * @return the object stored in this take as a tag, or {@code null} if not\n     * set.\n     * @see #setTag(int, Object)\n     * @see #getTag()\n     */\n    Object getTag(int key);\n\n\n    /**\n     * @deprecated Use {@link #isResuming()} instead.\n     */\n    boolean isContinue();\n\n    /**\n     * @return {@code true} if this task is resuming from the breakpoint, this value is valid\n     * after {@link FileDownloadListener#connected(BaseDownloadTask, String, boolean, int, int)}.\n     */\n    boolean isResuming();\n\n    /**\n     * @return The ETag from the response's header, this value is valid\n     * after {@link FileDownloadListener#connected(BaseDownloadTask, String, boolean, int, int)}\n     */\n    String getEtag();\n\n    /**\n     * @return The number of times has set to retry when occur any error.\n     * @see #setAutoRetryTimes(int)\n     */\n    int getAutoRetryTimes();\n\n    /**\n     * @return The currently number of times of retry.this value is valid\n     * after {@link FileDownloadListener#retry(BaseDownloadTask, Throwable, int, int)}\n     */\n    int getRetryingTimes();\n\n    /**\n     * @return {@code true} if the methods of {@link FileDownloadListener} will be invoked directly\n     * in message-thread for this task, {@code false} all methods of {@link FileDownloadListener}\n     * will\n     * be post to the UI thread for this task.\n     * @see #setSyncCallback(boolean)\n     */\n    boolean isSyncCallback();\n\n    /**\n     * @return {@code true} if the length of target file is more than or equal to 2G.\n     * @see #getLargeFileSoFarBytes()\n     * @see #getLargeFileTotalBytes()\n     */\n    boolean isLargeFile();\n\n    /**\n     * @return {@code true} if this task has been set only allows downloading on the wifi network\n     * type.\n     */\n    boolean isWifiRequired();\n\n    /**\n     * Declare the task will be assembled by a queue which makes up of the same listener task.\n     */\n    interface InQueueTask {\n        /**\n         * Enqueue the task to the global queue, what is the only way for the task to ready to be\n         * assembled by a queue.\n         * <p>\n         * <strong>Note:</strong> Only if this task belongs to a queue, you need to invoke this\n         * method.\n         *\n         * @return the download task identify.\n         */\n        int enqueue();\n    }\n\n    @SuppressWarnings(\"UnusedParameters\")\n    interface FinishListener {\n        /**\n         * Will be invoked when the {@code task} is over({@link FileDownloadStatus#isOver(int)}).\n         * This method will be invoked in Non-UI-Thread and this thread is controlled by\n         * {@link MessageSnapshotThreadPool}.\n         *\n         * @param task is over, the status would be one of below:\n         *             {@link FileDownloadStatus#completed}、{@link FileDownloadStatus#warn}、\n         *             {@link FileDownloadStatus#error}、{@link FileDownloadStatus#paused}.\n         * @see FileDownloadStatus#isOver(int)\n         */\n        void over(final BaseDownloadTask task);\n    }\n\n    /**\n     * The running task.\n     * <p>\n     * Used in internal.\n     */\n    interface IRunningTask {\n        /**\n         * @return The origin one.\n         */\n        BaseDownloadTask getOrigin();\n\n        /**\n         * @return The message handler of this task.\n         */\n        ITaskHunter.IMessageHandler getMessageHandler();\n\n        /**\n         * @return {@code true} the id of the task is equal to the {@code id}.\n         */\n        boolean is(int id);\n\n        /**\n         * @return {@code true} the listener of the task is equal to the {@code listener}.\n         */\n        boolean is(FileDownloadListener listener);\n\n        /**\n         * @return {@code true} if the task has already finished.\n         */\n        @SuppressWarnings(\"BooleanMethodIsAlwaysInverted\")\n        boolean isOver();\n\n        /**\n         * When the task is running, it must attach a key. if this task is running in a queue\n         * downloading tasks serial, the attach key is equal to the hash code of the callback of\n         * queue's handler, otherwise the attach key is equal to the hash code of the listener.\n         *\n         * @return The attached key, if this task in a queue, the attached key is the hash code of\n         * the listener.\n         */\n        int getAttachKey();\n\n        /**\n         * Set this task attach to the {@code key} by the queue. In this case, this task must be a\n         * task which belong to a queue, and will be started automatically by the queue.\n         *\n         * @param key The attached key for this task.\n         */\n        void setAttachKeyByQueue(int key);\n\n        /**\n         * Set this task to the default key. In this case, this task must be a task which is a\n         * isolated task.\n         */\n        void setAttachKeyDefault();\n\n        /**\n         * @return {@code true} the task has already added to the downloading list.\n         */\n        boolean isMarkedAdded2List();\n\n        /**\n         * Mark the task has already added to the downloading list.\n         */\n        void markAdded2List();\n\n        /**\n         * Free the task.\n         */\n        void free();\n\n        /**\n         * Start the task by the queue handler.\n         */\n        void startTaskByQueue();\n\n        /**\n         * Start the task just because this task can't started by pass, and now, we try to rescue\n         * this task and start it.\n         * <p>\n         * Currently, this rescue is occurred when the filedownloader service connected.\n         */\n        void startTaskByRescue();\n\n        /**\n         * Get the object as a lock for synchronized with the pause area.\n         *\n         * @return the object as a lock for synchronized with the pause area.\n         */\n        Object getPauseLock();\n\n        /**\n         * Whether contain finish listener or not.\n         *\n         * @return {@code true} if there is finish listener on the task.\n         */\n        boolean isContainFinishListener();\n    }\n\n    /**\n     * The callback for the life cycle of the task.\n     */\n    interface LifeCycleCallback {\n        /**\n         * The task begin working.\n         */\n        void onBegin();\n\n        /**\n         * The task is running, and during the downloading processing, when the status of the task\n         * is changed will trigger to callback this method.\n         */\n        void onIng();\n\n        /**\n         * The task is end.\n         */\n        void onOver();\n    }\n}\n"
  },
  {
    "path": "library/src/main/java/com/liulishuo/filedownloader/DownloadSpeedMonitor.java",
    "content": "/*\n * Copyright (c) 2015 LingoChamp Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *    http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.liulishuo.filedownloader;\n\nimport android.os.SystemClock;\n\n/**\n * The downloading speed monitor.\n */\n\npublic class DownloadSpeedMonitor implements IDownloadSpeed.Monitor, IDownloadSpeed.Lookup {\n\n    private long mLastRefreshTime;\n    private long mLastRefreshSofarBytes;\n    private long mStartSofarBytes;\n    private long mStartTime;\n    // KB/s\n    private int mSpeed;\n\n    private long mTotalBytes;\n\n    // The min interval millisecond for updating the download mSpeed.\n    private int mMinIntervalUpdateSpeed = 1000;\n\n    @Override\n    public void start(long startBytes) {\n        this.mStartTime = SystemClock.uptimeMillis();\n        this.mStartSofarBytes = startBytes;\n    }\n\n    @Override\n    public void end(long sofarBytes) {\n        if (mStartTime <= 0) {\n            return;\n        }\n\n        long downloadSize = sofarBytes - mStartSofarBytes;\n        this.mLastRefreshTime = 0;\n        long interval = SystemClock.uptimeMillis() - mStartTime;\n        if (interval <= 0) {\n            mSpeed = (int) downloadSize;\n        } else {\n            mSpeed = (int) (downloadSize / interval);\n        }\n    }\n\n    @Override\n    public void update(long sofarBytes) {\n        if (mMinIntervalUpdateSpeed <= 0) {\n            return;\n        }\n\n        boolean isUpdateData = false;\n        do {\n            if (mLastRefreshTime == 0) {\n                isUpdateData = true;\n                break;\n            }\n\n            long interval = SystemClock.uptimeMillis() - mLastRefreshTime;\n            if (interval >= mMinIntervalUpdateSpeed || (mSpeed == 0 && interval > 0)) {\n                mSpeed = (int) ((sofarBytes - mLastRefreshSofarBytes) / interval);\n                mSpeed = Math.max(0, mSpeed);\n                isUpdateData = true;\n                break;\n            }\n        } while (false);\n\n        if (isUpdateData) {\n            mLastRefreshSofarBytes = sofarBytes;\n            mLastRefreshTime = SystemClock.uptimeMillis();\n        }\n\n    }\n\n    @Override\n    public void reset() {\n        this.mSpeed = 0;\n        this.mLastRefreshTime = 0;\n    }\n\n    @Override\n    public int getSpeed() {\n        return this.mSpeed;\n    }\n\n    @Override\n    public void setMinIntervalUpdateSpeed(int minIntervalUpdateSpeed) {\n        this.mMinIntervalUpdateSpeed = minIntervalUpdateSpeed;\n    }\n}\n"
  },
  {
    "path": "library/src/main/java/com/liulishuo/filedownloader/DownloadTask.java",
    "content": "/*\n * Copyright (c) 2015 LingoChamp Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *    http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.liulishuo.filedownloader;\n\nimport android.text.TextUtils;\nimport android.util.SparseArray;\n\nimport com.liulishuo.filedownloader.model.FileDownloadHeader;\nimport com.liulishuo.filedownloader.model.FileDownloadModel;\nimport com.liulishuo.filedownloader.model.FileDownloadStatus;\nimport com.liulishuo.filedownloader.util.FileDownloadLog;\nimport com.liulishuo.filedownloader.util.FileDownloadUtils;\n\nimport java.io.File;\nimport java.util.ArrayList;\n\n/**\n * The download task.\n */\n\npublic class DownloadTask implements BaseDownloadTask, BaseDownloadTask.IRunningTask,\n        DownloadTaskHunter.ICaptureTask {\n\n    private final ITaskHunter mHunter;\n    private final ITaskHunter.IMessageHandler mMessageHandler;\n    private int mId;\n\n    private ArrayList<BaseDownloadTask.FinishListener> mFinishListenerList;\n\n    private final String mUrl;\n    private String mPath;\n    private String mFilename;\n    private boolean mPathAsDirectory;\n\n    private FileDownloadHeader mHeader;\n\n    private FileDownloadListener mListener;\n\n    private SparseArray<Object> mKeyedTags;\n    private Object mTag;\n\n    private int mAutoRetryTimes = 0;\n\n    /**\n     * If {@code true} will callback directly on the download thread(do not on post the message to\n     * the ui thread\n     * by {@link android.os.Handler#post(Runnable)}\n     */\n    private boolean mSyncCallback = false;\n\n    private boolean mIsWifiRequired = false;\n\n    public static final int DEFAULT_CALLBACK_PROGRESS_MIN_INTERVAL_MILLIS = 10;\n    private int mCallbackProgressTimes = FileDownloadModel.DEFAULT_CALLBACK_PROGRESS_TIMES;\n    private int mCallbackProgressMinIntervalMillis = DEFAULT_CALLBACK_PROGRESS_MIN_INTERVAL_MILLIS;\n\n    private boolean mIsForceReDownload = false;\n\n    volatile int mAttachKey = 0;\n    private boolean mIsInQueueTask = false;\n\n    DownloadTask(final String url) {\n        this.mUrl = url;\n        mPauseLock = new Object();\n        final DownloadTaskHunter hunter = new DownloadTaskHunter(this, mPauseLock);\n\n        mHunter = hunter;\n        mMessageHandler = hunter;\n    }\n\n    @Override\n    public BaseDownloadTask setMinIntervalUpdateSpeed(int minIntervalUpdateSpeedMs) {\n        mHunter.setMinIntervalUpdateSpeed(minIntervalUpdateSpeedMs);\n        return this;\n    }\n\n    @Override\n    public BaseDownloadTask setPath(final String path) {\n        return setPath(path, false);\n    }\n\n    @Override\n    public BaseDownloadTask setPath(final String path, final boolean pathAsDirectory) {\n        this.mPath = path;\n        if (FileDownloadLog.NEED_LOG) {\n            FileDownloadLog.d(this, \"setPath %s\", path);\n        }\n\n        this.mPathAsDirectory = pathAsDirectory;\n        if (pathAsDirectory) {\n            /**\n             * will be found before the callback of\n             * {@link FileDownloadListener#connected(BaseDownloadTask, String, boolean, int, int)}\n             */\n            this.mFilename = null;\n        } else {\n            this.mFilename = new File(path).getName();\n        }\n\n        return this;\n    }\n\n    @Override\n    public BaseDownloadTask setListener(final FileDownloadListener listener) {\n        this.mListener = listener;\n\n        if (FileDownloadLog.NEED_LOG) {\n            FileDownloadLog.d(this, \"setListener %s\", listener);\n        }\n        return this;\n    }\n\n    @Override\n    public BaseDownloadTask setCallbackProgressTimes(int callbackProgressCount) {\n        this.mCallbackProgressTimes = callbackProgressCount;\n        return this;\n    }\n\n    @Override\n    public BaseDownloadTask setCallbackProgressMinInterval(int minIntervalMillis) {\n        this.mCallbackProgressMinIntervalMillis = minIntervalMillis;\n        return this;\n    }\n\n    @Override\n    public BaseDownloadTask setCallbackProgressIgnored() {\n        return setCallbackProgressTimes(-1);\n    }\n\n    @Override\n    public BaseDownloadTask setTag(final Object tag) {\n        this.mTag = tag;\n        if (FileDownloadLog.NEED_LOG) {\n            FileDownloadLog.d(this, \"setTag %s\", tag);\n        }\n        return this;\n    }\n\n    @Override\n    public BaseDownloadTask setTag(final int key, final Object tag) {\n        if (mKeyedTags == null) {\n            mKeyedTags = new SparseArray<>(2);\n        }\n        mKeyedTags.put(key, tag);\n        return this;\n    }\n\n    @Override\n    public BaseDownloadTask setForceReDownload(final boolean isForceReDownload) {\n        this.mIsForceReDownload = isForceReDownload;\n        return this;\n    }\n\n    @Override\n    public BaseDownloadTask setFinishListener(\n            final BaseDownloadTask.FinishListener finishListener) {\n        addFinishListener(finishListener);\n        return this;\n    }\n\n    @Override\n    public BaseDownloadTask addFinishListener(\n            final BaseDownloadTask.FinishListener finishListener) {\n        if (mFinishListenerList == null) {\n            mFinishListenerList = new ArrayList<>();\n        }\n\n        if (!mFinishListenerList.contains(finishListener)) {\n            mFinishListenerList.add(finishListener);\n        }\n        return this;\n    }\n\n    @Override\n    public boolean removeFinishListener(final BaseDownloadTask.FinishListener finishListener) {\n        return mFinishListenerList != null && mFinishListenerList.remove(finishListener);\n    }\n\n    @Override\n    public BaseDownloadTask setAutoRetryTimes(int autoRetryTimes) {\n        this.mAutoRetryTimes = autoRetryTimes;\n        return this;\n    }\n\n    @Override\n    public BaseDownloadTask addHeader(final String name, final String value) {\n        checkAndCreateHeader();\n        mHeader.add(name, value);\n        return this;\n    }\n\n    @Override\n    public BaseDownloadTask addHeader(final String line) {\n        checkAndCreateHeader();\n        mHeader.add(line);\n        return this;\n    }\n\n    @Override\n    public BaseDownloadTask removeAllHeaders(final String name) {\n        if (mHeader == null) {\n            synchronized (headerCreateLock) {\n                // maybe invoking checkAndCreateHear and will to be available.\n                if (mHeader == null) {\n                    return this;\n                }\n            }\n        }\n\n\n        mHeader.removeAll(name);\n        return this;\n    }\n\n    @Override\n    public BaseDownloadTask setSyncCallback(final boolean syncCallback) {\n        this.mSyncCallback = syncCallback;\n        return this;\n    }\n\n    @Override\n    public BaseDownloadTask setWifiRequired(boolean isWifiRequired) {\n        this.mIsWifiRequired = isWifiRequired;\n        return this;\n    }\n\n    @Override\n    public int ready() {\n        return asInQueueTask().enqueue();\n    }\n\n    @Override\n    public InQueueTask asInQueueTask() {\n        return new InQueueTaskImpl(this);\n    }\n\n    @Override\n    public boolean reuse() {\n        if (isRunning()) {\n            FileDownloadLog.w(this, \"This task[%d] is running, if you want start the same task,\"\n                    + \" please create a new one by FileDownloader#create\", getId());\n            return false;\n        }\n\n        this.mAttachKey = 0;\n        mIsInQueueTask = false;\n        mIsMarkedAdded2List = false;\n        mHunter.reset();\n\n        return true;\n    }\n\n    @Override\n    public boolean isUsing() {\n        return mHunter.getStatus() != FileDownloadStatus.INVALID_STATUS;\n    }\n\n\n    @Override\n    public boolean isRunning() {\n        //noinspection SimplifiableIfStatement\n        if (FileDownloader.getImpl().getLostConnectedHandler().isInWaitingList(this)) {\n            return true;\n        }\n\n        return FileDownloadStatus.isIng(getStatus());\n    }\n\n    @Override\n    public boolean isAttached() {\n        return mAttachKey != 0;\n    }\n\n    @Override\n    public int start() {\n        if (mIsInQueueTask) {\n            throw new IllegalStateException(\"If you start the task manually, it means this task \"\n                    + \"doesn't belong to a queue, so you must not invoke BaseDownloadTask#ready() \"\n                    + \"or InQueueTask#enqueue() before you start() this method. For detail: If this\"\n                    + \" task doesn't belong to a queue, what is just an isolated task, you just \"\n                    + \"need to invoke BaseDownloadTask#start() to start this task, that's all. In\"\n                    + \" other words, If this task doesn't belong to a queue, you must not invoke\"\n                    + \" BaseDownloadTask#ready() method or InQueueTask#enqueue() method before\"\n                    + \" invoke BaseDownloadTask#start(), If you do that and if there is the same\"\n                    + \" listener object to start a queue in another thread, this task may be \"\n                    + \"assembled by the queue, in that case, when you invoke \"\n                    + \"BaseDownloadTask#start() manually to start this task or this task is started\"\n                    + \" by the queue, there is an exception buried in there, because this task\"\n                    + \" object is started two times without declare BaseDownloadTask#reuse() :\"\n                    + \" 1. you invoke BaseDownloadTask#start() manually; \"\n                    + \" 2. the queue start this task automatically.\");\n        }\n\n        return startTaskUnchecked();\n    }\n\n    private int startTaskUnchecked() {\n        if (isUsing()) {\n            if (isRunning()) {\n                throw new IllegalStateException(\n                        FileDownloadUtils.formatString(\"This task is running %d, if you\"\n                                + \" want to start the same task, please create a new one by\"\n                                + \" FileDownloader.create\", getId()));\n            } else {\n                throw new IllegalStateException(\"This task is dirty to restart, If you want to \"\n                        + \"reuse this task, please invoke #reuse method manually and retry to \"\n                        + \"restart again.\" + mHunter.toString());\n            }\n        }\n\n        if (!isAttached()) {\n            setAttachKeyDefault();\n        }\n\n        mHunter.intoLaunchPool();\n\n        return getId();\n    }\n\n    // -------------- Another Operations ---------------------\n\n    private final Object mPauseLock;\n\n    @Override\n    public boolean pause() {\n        synchronized (mPauseLock) {\n            return mHunter.pause();\n        }\n    }\n\n    @Override\n    public boolean cancel() {\n        return pause();\n    }\n\n    // ------------------- Get -----------------------\n\n    @Override\n    public int getId() {\n        if (mId != 0) {\n            return mId;\n        }\n\n        if (!TextUtils.isEmpty(mPath) && !TextUtils.isEmpty(mUrl)) {\n            return mId = FileDownloadUtils.generateId(mUrl, mPath, mPathAsDirectory);\n        }\n\n        return 0;\n    }\n\n    /**\n     * @deprecated Used {@link #getId()} instead.\n     */\n    @Override\n    public int getDownloadId() {\n        return getId();\n    }\n\n    @Override\n    public String getUrl() {\n        return mUrl;\n    }\n\n    @Override\n    public int getCallbackProgressTimes() {\n        return mCallbackProgressTimes;\n    }\n\n    @Override\n    public int getCallbackProgressMinInterval() {\n        return mCallbackProgressMinIntervalMillis;\n    }\n\n    @Override\n    public String getPath() {\n        return mPath;\n    }\n\n    @Override\n    public boolean isPathAsDirectory() {\n        return mPathAsDirectory;\n    }\n\n    @Override\n    public String getFilename() {\n        return mFilename;\n    }\n\n    @Override\n    public String getTargetFilePath() {\n        return FileDownloadUtils.getTargetFilePath(getPath(), isPathAsDirectory(), getFilename());\n    }\n\n    @Override\n    public FileDownloadListener getListener() {\n        return mListener;\n    }\n\n    @Override\n    public int getSoFarBytes() {\n        return getSmallFileSoFarBytes();\n    }\n\n    @Override\n    public int getSmallFileSoFarBytes() {\n        if (mHunter.getSofarBytes() > Integer.MAX_VALUE) {\n            return Integer.MAX_VALUE;\n        }\n        return (int) mHunter.getSofarBytes();\n    }\n\n    @Override\n    public long getLargeFileSoFarBytes() {\n        return mHunter.getSofarBytes();\n    }\n\n    @Override\n    public int getTotalBytes() {\n        return getSmallFileTotalBytes();\n    }\n\n    @Override\n    public int getSmallFileTotalBytes() {\n        if (mHunter.getTotalBytes() > Integer.MAX_VALUE) {\n            return Integer.MAX_VALUE;\n        }\n\n        return (int) mHunter.getTotalBytes();\n    }\n\n    @Override\n    public long getLargeFileTotalBytes() {\n        return mHunter.getTotalBytes();\n    }\n\n    @Override\n    public int getSpeed() {\n        return mHunter.getSpeed();\n    }\n\n    @Override\n    public byte getStatus() {\n        return mHunter.getStatus();\n    }\n\n    @Override\n    public boolean isForceReDownload() {\n        return this.mIsForceReDownload;\n    }\n\n    @Override\n    public Throwable getEx() {\n        return getErrorCause();\n    }\n\n    @Override\n    public Throwable getErrorCause() {\n        return mHunter.getErrorCause();\n    }\n\n    @Override\n    public boolean isReusedOldFile() {\n        return mHunter.isReusedOldFile();\n    }\n\n    @Override\n    public Object getTag() {\n        return this.mTag;\n    }\n\n    @Override\n    public Object getTag(int key) {\n        return mKeyedTags == null ? null : mKeyedTags.get(key);\n    }\n\n\n    /**\n     * @deprecated Use {@link #isResuming()} instead.\n     */\n    @Override\n    public boolean isContinue() {\n        return isResuming();\n    }\n\n    @Override\n    public boolean isResuming() {\n        return mHunter.isResuming();\n    }\n\n    @Override\n    public String getEtag() {\n        return mHunter.getEtag();\n    }\n\n    @Override\n    public int getAutoRetryTimes() {\n        return this.mAutoRetryTimes;\n    }\n\n    @Override\n    public int getRetryingTimes() {\n        return mHunter.getRetryingTimes();\n    }\n\n    @Override\n    public boolean isSyncCallback() {\n        return mSyncCallback;\n    }\n\n    @Override\n    public boolean isLargeFile() {\n        return mHunter.isLargeFile();\n    }\n\n    @Override\n    public boolean isWifiRequired() {\n        return mIsWifiRequired;\n    }\n\n    private final Object headerCreateLock = new Object();\n\n    private void checkAndCreateHeader() {\n        if (mHeader == null) {\n            synchronized (headerCreateLock) {\n                if (mHeader == null) {\n                    mHeader = new FileDownloadHeader();\n                }\n            }\n        }\n    }\n\n    @Override\n    public FileDownloadHeader getHeader() {\n        return this.mHeader;\n    }\n\n\n    // why this? thread not safe: update,InQueueTask#enqueue, start, pause, start which influence of\n    // this\n    // in the queue.\n    // whether it has been added, whether or not it is removed.\n    private volatile boolean mIsMarkedAdded2List = false;\n\n    @Override\n    public void markAdded2List() {\n        mIsMarkedAdded2List = true;\n    }\n\n    @Override\n    public void free() {\n        mHunter.free();\n        if (FileDownloadList.getImpl().isNotContains(this)) {\n            mIsMarkedAdded2List = false;\n        }\n    }\n\n    @Override\n    public void startTaskByQueue() {\n        startTaskUnchecked();\n    }\n\n    @Override\n    public void startTaskByRescue() {\n        // In this case, we don't need to check, because, we just to rescue this task, it means this\n        // task has already called start, but the filedownloader service didn't connected, and now,\n        // the service is connected, so we just rescue this task.\n        startTaskUnchecked();\n    }\n\n    @Override\n    public Object getPauseLock() {\n        return mPauseLock;\n    }\n\n    @Override\n    public boolean isContainFinishListener() {\n        return mFinishListenerList != null && mFinishListenerList.size() > 0;\n    }\n\n\n    @Override\n    public boolean isMarkedAdded2List() {\n        return this.mIsMarkedAdded2List;\n    }\n\n    @Override\n    public BaseDownloadTask.IRunningTask getRunningTask() {\n        return this;\n    }\n\n    @Override\n    public void setFileName(String fileName) {\n        mFilename = fileName;\n    }\n\n    @Override\n    public ArrayList<FinishListener> getFinishListenerList() {\n        return mFinishListenerList;\n    }\n\n    @Override\n    public BaseDownloadTask getOrigin() {\n        return this;\n    }\n\n    @Override\n    public ITaskHunter.IMessageHandler getMessageHandler() {\n        return mMessageHandler;\n    }\n\n    @Override\n    public boolean is(int id) {\n        return getId() == id;\n    }\n\n    @Override\n    public boolean is(FileDownloadListener listener) {\n        return getListener() == listener;\n    }\n\n    @Override\n    public boolean isOver() {\n        return FileDownloadStatus.isOver(getStatus());\n    }\n\n    @Override\n    public int getAttachKey() {\n        return mAttachKey;\n    }\n\n    @Override\n    public void setAttachKeyByQueue(int key) {\n        this.mAttachKey = key;\n    }\n\n    @Override\n    public void setAttachKeyDefault() {\n        final int attachKey;\n        if (getListener() != null) {\n            attachKey = getListener().hashCode();\n        } else {\n            attachKey = hashCode();\n        }\n        this.mAttachKey = attachKey;\n    }\n\n    @Override\n    public String toString() {\n        return FileDownloadUtils.formatString(\"%d@%s\", getId(), super.toString());\n    }\n\n    private static final class InQueueTaskImpl implements InQueueTask {\n        private final DownloadTask mTask;\n\n        private InQueueTaskImpl(DownloadTask task) {\n            this.mTask = task;\n            this.mTask.mIsInQueueTask = true;\n        }\n\n        @Override\n        public int enqueue() {\n            final int id = mTask.getId();\n\n            if (FileDownloadLog.NEED_LOG) {\n                FileDownloadLog.d(this, \"add the task[%d] to the queue\", id);\n            }\n\n            FileDownloadList.getImpl().addUnchecked(mTask);\n            return id;\n        }\n    }\n}\n"
  },
  {
    "path": "library/src/main/java/com/liulishuo/filedownloader/DownloadTaskHunter.java",
    "content": "/*\n * Copyright (c) 2015 LingoChamp Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *    http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.liulishuo.filedownloader;\n\nimport com.liulishuo.filedownloader.message.MessageSnapshot;\nimport com.liulishuo.filedownloader.message.MessageSnapshotTaker;\nimport com.liulishuo.filedownloader.model.FileDownloadHeader;\nimport com.liulishuo.filedownloader.model.FileDownloadStatus;\nimport com.liulishuo.filedownloader.util.FileDownloadHelper;\nimport com.liulishuo.filedownloader.util.FileDownloadLog;\nimport com.liulishuo.filedownloader.util.FileDownloadUtils;\n\nimport java.io.File;\nimport java.io.IOException;\nimport java.security.InvalidParameterException;\nimport java.util.ArrayList;\n\n/**\n * The download task hunter.\n */\n\npublic class DownloadTaskHunter implements ITaskHunter, ITaskHunter.IStarter,\n        ITaskHunter.IMessageHandler,\n        BaseDownloadTask.LifeCycleCallback {\n\n    private IFileDownloadMessenger mMessenger;\n    private final Object mPauseLock;\n\n    @Override\n    public boolean updateKeepAhead(MessageSnapshot snapshot) {\n        if (!FileDownloadStatus.isKeepAhead(getStatus(), snapshot.getStatus())) {\n            if (FileDownloadLog.NEED_LOG) {\n                FileDownloadLog.d(this, \"can't update mStatus change by keep ahead, %d, but the\"\n                        + \" current mStatus is %d, %d\", mStatus, getStatus(), getId());\n            }\n            return false;\n        }\n\n        update(snapshot);\n        return true;\n    }\n\n    @Override\n    public boolean updateKeepFlow(MessageSnapshot snapshot) {\n        final int currentStatus = getStatus();\n        final int nextStatus = snapshot.getStatus();\n\n        if (FileDownloadStatus.paused == currentStatus && FileDownloadStatus.isIng(nextStatus)) {\n            if (FileDownloadLog.NEED_LOG) {\n                /**\n                 * Occur such situation, must be the running-mStatus waiting for turning up in flow\n                 * thread pool(or binder thread) when there is someone invoked the {@link #pause()}.\n                 *\n                 * High concurrent cause.\n                 */\n                FileDownloadLog.d(this, \"High concurrent cause, callback pending, but has already\"\n                        + \" be paused %d\", getId());\n            }\n            return true;\n        }\n\n        if (!FileDownloadStatus.isKeepFlow(currentStatus, nextStatus)) {\n            if (FileDownloadLog.NEED_LOG) {\n                FileDownloadLog.d(this, \"can't update mStatus change by keep flow, %d, but the\"\n                        + \" current mStatus is %d, %d\", mStatus, getStatus(), getId());\n            }\n\n            return false;\n        }\n\n        update(snapshot);\n        return true;\n    }\n\n    @Override\n    public boolean updateMoreLikelyCompleted(MessageSnapshot snapshot) {\n        if (!FileDownloadStatus.isMoreLikelyCompleted(mTask.getRunningTask().getOrigin())) {\n            return false;\n        }\n\n        update(snapshot);\n        return true;\n    }\n\n    @Override\n    public boolean updateSameFilePathTaskRunning(MessageSnapshot snapshot) {\n        if (!mTask.getRunningTask().getOrigin().isPathAsDirectory()) {\n            return false;\n        }\n\n        if (snapshot.getStatus() != FileDownloadStatus.warn\n                || getStatus() != FileDownloadStatus.connected) {\n            return false;\n        }\n\n        update(snapshot);\n        return true;\n    }\n\n    @Override\n    public IFileDownloadMessenger getMessenger() {\n        return mMessenger;\n    }\n\n    @Override\n    public MessageSnapshot prepareErrorMessage(Throwable cause) {\n        mStatus = FileDownloadStatus.error;\n        mThrowable = cause;\n        return MessageSnapshotTaker.catchException(getId(), getSofarBytes(), cause);\n    }\n\n    @SuppressWarnings(\"checkstyle:emptyblock\")\n    private void update(final MessageSnapshot snapshot) {\n        final BaseDownloadTask task = mTask.getRunningTask().getOrigin();\n        final byte status = snapshot.getStatus();\n\n        this.mStatus = status;\n        this.mIsLargeFile = snapshot.isLargeFile();\n\n        switch (status) {\n            case FileDownloadStatus.pending:\n                this.mSoFarBytes = snapshot.getLargeSofarBytes();\n                this.mTotalBytes = snapshot.getLargeTotalBytes();\n\n                // notify\n                mMessenger.notifyPending(snapshot);\n                break;\n            case FileDownloadStatus.started:\n                // notify\n                mMessenger.notifyStarted(snapshot);\n                break;\n            case FileDownloadStatus.connected:\n                this.mTotalBytes = snapshot.getLargeTotalBytes();\n                this.mIsResuming = snapshot.isResuming();\n                this.mEtag = snapshot.getEtag();\n\n                final String filename = snapshot.getFileName();\n                if (filename != null) {\n                    if (task.getFilename() != null) {\n                        FileDownloadLog.w(this,\n                                \"already has mFilename[%s], but assign mFilename[%s] again\",\n                                task.getFilename(), filename);\n                    }\n                    mTask.setFileName(filename);\n                }\n\n                mSpeedMonitor.start(mSoFarBytes);\n\n                // notify\n                mMessenger.notifyConnected(snapshot);\n                break;\n            case FileDownloadStatus.progress:\n                this.mSoFarBytes = snapshot.getLargeSofarBytes();\n                mSpeedMonitor.update(snapshot.getLargeSofarBytes());\n\n                // notify\n                mMessenger.notifyProgress(snapshot);\n                break;\n//            case FileDownloadStatus.blockComplete:\n            /**\n             * Handled by {@link FileDownloadList#removeByCompleted(BaseDownloadTask)}\n             */\n//                break;\n            case FileDownloadStatus.retry:\n                this.mSoFarBytes = snapshot.getLargeSofarBytes();\n                this.mThrowable = snapshot.getThrowable();\n                mRetryingTimes = snapshot.getRetryingTimes();\n\n                mSpeedMonitor.reset();\n\n                // notify\n                mMessenger.notifyRetry(snapshot);\n                break;\n            case FileDownloadStatus.error:\n                this.mThrowable = snapshot.getThrowable();\n                this.mSoFarBytes = snapshot.getLargeSofarBytes();\n\n                // to FileDownloadList\n                FileDownloadList.getImpl().remove(mTask.getRunningTask(), snapshot);\n\n                break;\n            case FileDownloadStatus.paused:\n                /**\n                 * Handled by {@link #pause()}\n                 */\n                break;\n            case FileDownloadStatus.completed:\n                this.mIsReusedOldFile = snapshot.isReusedDownloadedFile();\n                // only carry total data back\n                this.mSoFarBytes = snapshot.getLargeTotalBytes();\n                this.mTotalBytes = snapshot.getLargeTotalBytes();\n\n                // to FileDownloadList\n                FileDownloadList.getImpl().remove(mTask.getRunningTask(), snapshot);\n\n                break;\n            case FileDownloadStatus.warn:\n                mSpeedMonitor.reset();\n\n                final int sameIdTaskCount = FileDownloadList.getImpl().count(task.getId());\n\n                final int sameStoreTaskCount;\n                // generate same task id.\n                if (sameIdTaskCount <= 1 && task.isPathAsDirectory()) {\n                    sameStoreTaskCount = FileDownloadList.getImpl().count(FileDownloadUtils.\n                            generateId(task.getUrl(), task.getTargetFilePath()));\n                } else {\n                    sameStoreTaskCount = 0;\n                }\n\n                if (sameIdTaskCount + sameStoreTaskCount <= 1) {\n                    // 1. this progress kill by sys and relive,\n                    // for add at least one mListener\n                    // or 2. pre downloading task has already completed/error/paused\n                    // request mStatus\n                    final int currentStatus = FileDownloadServiceProxy.getImpl().\n                            getStatus(task.getId());\n                    FileDownloadLog.w(this, \"warn, but no mListener to receive, \"\n                            + \"switch to pending %d %d\", task.getId(), currentStatus);\n\n                    //noinspection StatementWithEmptyBody\n                    if (FileDownloadStatus.isIng(currentStatus)) {\n                        // ing, has callbacks\n                        // keep and wait callback\n\n                        this.mStatus = FileDownloadStatus.pending;\n                        this.mTotalBytes = snapshot.getLargeTotalBytes();\n                        this.mSoFarBytes = snapshot.getLargeSofarBytes();\n\n                        mSpeedMonitor.start(mSoFarBytes);\n\n                        mMessenger.\n                                notifyPending(((MessageSnapshot.IWarnMessageSnapshot) snapshot).\n                                        turnToPending());\n                        break;\n                    } else {\n                        // already over and no callback\n                    }\n\n                }\n\n                // to FileDownloadList\n                FileDownloadList.getImpl().remove(mTask.getRunningTask(), snapshot);\n                break;\n            default:\n                // ignored\n        }\n    }\n\n    @Override\n    public void onBegin() {\n        if (FileDownloadMonitor.isValid()) {\n            FileDownloadMonitor.getMonitor().onTaskBegin(mTask.getRunningTask().getOrigin());\n        }\n\n        if (FileDownloadLog.NEED_LOG) {\n            FileDownloadLog\n                    .v(this, \"filedownloader:lifecycle:start %s by %d \", toString(), getStatus());\n        }\n    }\n\n    @Override\n    public void onIng() {\n        if (FileDownloadMonitor.isValid() && getStatus() == FileDownloadStatus.started) {\n            FileDownloadMonitor.getMonitor().onTaskStarted(mTask.getRunningTask().getOrigin());\n        }\n    }\n\n    @Override\n    public void onOver() {\n        final BaseDownloadTask origin = mTask.getRunningTask().getOrigin();\n\n        if (FileDownloadMonitor.isValid()) {\n            FileDownloadMonitor.getMonitor().onTaskOver(origin);\n        }\n\n        if (FileDownloadLog.NEED_LOG) {\n            FileDownloadLog.v(this, \"filedownloader:lifecycle:over %s by %d \", toString(),\n                    getStatus());\n        }\n\n        mSpeedMonitor.end(this.mSoFarBytes);\n        if (mTask.getFinishListenerList() != null) {\n            @SuppressWarnings(\"unchecked\") final ArrayList<BaseDownloadTask.FinishListener>\n                    listenersCopy =\n                    (ArrayList<BaseDownloadTask.FinishListener>) mTask.getFinishListenerList()\n                            .clone();\n            final int numListeners = listenersCopy.size();\n            for (int i = 0; i < numListeners; ++i) {\n                listenersCopy.get(i).over(origin);\n            }\n        }\n\n        FileDownloader.getImpl().getLostConnectedHandler().taskWorkFine(mTask.getRunningTask());\n    }\n\n    interface ICaptureTask {\n        FileDownloadHeader getHeader();\n\n        BaseDownloadTask.IRunningTask getRunningTask();\n\n        void setFileName(String fileName);\n\n        ArrayList<BaseDownloadTask.FinishListener> getFinishListenerList();\n    }\n\n    private final ICaptureTask mTask;\n    private volatile byte mStatus = FileDownloadStatus.INVALID_STATUS;\n    private Throwable mThrowable = null;\n\n    private final IDownloadSpeed.Monitor mSpeedMonitor;\n    private final IDownloadSpeed.Lookup mSpeedLookup;\n\n    private long mSoFarBytes;\n    private long mTotalBytes;\n\n    private int mRetryingTimes;\n\n    private boolean mIsLargeFile;\n\n    private boolean mIsResuming;\n    private String mEtag;\n\n    private boolean mIsReusedOldFile = false;\n\n    DownloadTaskHunter(ICaptureTask task, Object pauseLock) {\n        mPauseLock = pauseLock;\n        mTask = task;\n        final DownloadSpeedMonitor monitor = new DownloadSpeedMonitor();\n        mSpeedMonitor = monitor;\n        mSpeedLookup = monitor;\n        mMessenger = new FileDownloadMessenger(task.getRunningTask(), this);\n    }\n\n    @Override\n    public void intoLaunchPool() {\n        synchronized (mPauseLock) {\n            if (mStatus != FileDownloadStatus.INVALID_STATUS) {\n                FileDownloadLog.w(this, \"High concurrent cause, this task %d will not input \"\n                                + \"to launch pool, because of the status isn't idle : %d\",\n                        getId(), mStatus);\n                return;\n            }\n\n            mStatus = FileDownloadStatus.toLaunchPool;\n        }\n\n        final BaseDownloadTask.IRunningTask runningTask = mTask.getRunningTask();\n        final BaseDownloadTask origin = runningTask.getOrigin();\n\n        if (FileDownloadMonitor.isValid()) {\n            FileDownloadMonitor.getMonitor().onRequestStart(origin);\n        }\n\n        if (FileDownloadLog.NEED_LOG) {\n            FileDownloadLog.v(this, \"call start \"\n                            + \"Url[%s], Path[%s] Listener[%s], Tag[%s]\",\n                    origin.getUrl(), origin.getPath(), origin.getListener(), origin.getTag());\n        }\n\n        boolean ready = true;\n\n        try {\n            prepare();\n        } catch (Throwable e) {\n            ready = false;\n\n            FileDownloadList.getImpl().add(runningTask);\n            FileDownloadList.getImpl().remove(runningTask, prepareErrorMessage(e));\n        }\n\n        if (ready) {\n            FileDownloadTaskLauncher.getImpl().launch(this);\n        }\n\n        if (FileDownloadLog.NEED_LOG) {\n            FileDownloadLog.v(this, \"the task[%d] has been into the launch pool.\", getId());\n        }\n    }\n\n    @Override\n    public boolean pause() {\n        if (FileDownloadStatus.isOver(getStatus())) {\n            if (FileDownloadLog.NEED_LOG) {\n                /**\n                 * The over-mStatus call-backed and set the over-mStatus to this task between here\n                 * area and remove from the {@link FileDownloadList}.\n                 *\n                 * High concurrent cause.\n                 */\n                FileDownloadLog.d(this, \"High concurrent cause, Already is over, can't pause \"\n                        + \"again, %d %d\", getStatus(), mTask.getRunningTask().getOrigin().getId());\n            }\n            return false;\n        }\n        this.mStatus = FileDownloadStatus.paused;\n\n        final BaseDownloadTask.IRunningTask runningTask = mTask.getRunningTask();\n        final BaseDownloadTask origin = runningTask.getOrigin();\n\n        FileDownloadTaskLauncher.getImpl().expire(this);\n        if (FileDownloadLog.NEED_LOG) {\n            FileDownloadLog.v(this, \"the task[%d] has been expired from the launch pool.\", getId());\n        }\n\n        if (!FileDownloader.getImpl().isServiceConnected()) {\n            if (FileDownloadLog.NEED_LOG) {\n                FileDownloadLog.d(this, \"request pause the task[%d] to the download service,\"\n                        + \" but the download service isn't connected yet.\", origin.getId());\n            }\n        } else {\n            FileDownloadServiceProxy.getImpl().pause(origin.getId());\n        }\n\n        // For make sure already added event mListener for receive paused event\n        FileDownloadList.getImpl().add(runningTask);\n        FileDownloadList.getImpl().remove(runningTask, MessageSnapshotTaker.catchPause(origin));\n\n        FileDownloader.getImpl().getLostConnectedHandler().taskWorkFine(runningTask);\n\n        return true;\n    }\n\n    @Override\n    public byte getStatus() {\n        return mStatus;\n    }\n\n    @Override\n    public void reset() {\n        mThrowable = null;\n\n        mEtag = null;\n        mIsResuming = false;\n        mRetryingTimes = 0;\n        mIsReusedOldFile = false;\n        mIsLargeFile = false;\n\n        mSoFarBytes = 0;\n        mTotalBytes = 0;\n\n        mSpeedMonitor.reset();\n\n        if (FileDownloadStatus.isOver(mStatus)) {\n            mMessenger.discard();\n            mMessenger = new FileDownloadMessenger(mTask.getRunningTask(), this);\n        } else {\n            mMessenger.reAppointment(mTask.getRunningTask(), this);\n        }\n\n        mStatus = FileDownloadStatus.INVALID_STATUS;\n    }\n\n    @Override\n    public void setMinIntervalUpdateSpeed(int minIntervalUpdateSpeed) {\n        mSpeedLookup.setMinIntervalUpdateSpeed(minIntervalUpdateSpeed);\n    }\n\n    @Override\n    public int getSpeed() {\n        return mSpeedLookup.getSpeed();\n    }\n\n    @Override\n    public long getSofarBytes() {\n        return mSoFarBytes;\n    }\n\n    @Override\n    public long getTotalBytes() {\n        return mTotalBytes;\n    }\n\n    @Override\n    public Throwable getErrorCause() {\n        return mThrowable;\n    }\n\n    @Override\n    public int getRetryingTimes() {\n        return mRetryingTimes;\n    }\n\n    @Override\n    public boolean isReusedOldFile() {\n        return mIsReusedOldFile;\n    }\n\n    @Override\n    public boolean isResuming() {\n        return mIsResuming;\n    }\n\n    @Override\n    public String getEtag() {\n        return mEtag;\n    }\n\n    @Override\n    public boolean isLargeFile() {\n        return mIsLargeFile;\n    }\n\n    @Override\n    public void free() {\n        if (FileDownloadLog.NEED_LOG) {\n            FileDownloadLog.d(this, \"free the task %d, when the status is %d\", getId(), mStatus);\n        }\n        mStatus = FileDownloadStatus.INVALID_STATUS;\n    }\n\n    private void prepare() throws IOException {\n        final BaseDownloadTask.IRunningTask runningTask = mTask.getRunningTask();\n        final BaseDownloadTask origin = runningTask.getOrigin();\n\n        if (origin.getPath() == null) {\n            origin.setPath(FileDownloadUtils.getDefaultSaveFilePath(origin.getUrl()));\n            if (FileDownloadLog.NEED_LOG) {\n                FileDownloadLog.d(this, \"save Path is null to %s\", origin.getPath());\n            }\n        }\n\n        final File dir;\n        if (origin.isPathAsDirectory()) {\n            dir = new File(origin.getPath());\n        } else {\n            final String dirString = FileDownloadUtils.getParent(origin.getPath());\n            if (dirString == null) {\n                throw new InvalidParameterException(\n                        FileDownloadUtils.formatString(\"the provided mPath[%s] is invalid,\"\n                                + \" can't find its directory\", origin.getPath()));\n            }\n            dir = new File(dirString);\n        }\n\n        if (!dir.exists()) {\n            if (!dir.mkdirs() && !dir.exists()) {\n                throw new IOException(FileDownloadUtils.\n                        formatString(\"Create parent directory failed, please make sure \"\n                                        + \"you have permission to create file or directory \"\n                                        + \"on the path: %s\",\n                                dir.getAbsolutePath()));\n            }\n        }\n    }\n\n    private int getId() {\n        return mTask.getRunningTask().getOrigin().getId();\n    }\n\n    @SuppressWarnings(\"checkstyle:emptyblock\")\n    @Override\n    public void start() {\n        if (mStatus != FileDownloadStatus.toLaunchPool) {\n            FileDownloadLog.w(this, \"High concurrent cause, this task %d will not start,\"\n                            + \" because the of status isn't toLaunchPool: %d\",\n                    getId(), mStatus);\n            return;\n        }\n\n        final BaseDownloadTask.IRunningTask runningTask = mTask.getRunningTask();\n        final BaseDownloadTask origin = runningTask.getOrigin();\n\n        final ILostServiceConnectedHandler lostConnectedHandler = FileDownloader.getImpl().\n                getLostConnectedHandler();\n        try {\n\n            if (lostConnectedHandler.dispatchTaskStart(runningTask)) {\n                return;\n            }\n\n            synchronized (mPauseLock) {\n                if (mStatus != FileDownloadStatus.toLaunchPool) {\n                    FileDownloadLog.w(this, \"High concurrent cause, this task %d will not start,\"\n                                    + \" the status can't assign to toFileDownloadService, because \"\n                                    + \"the status isn't toLaunchPool: %d\",\n                            getId(), mStatus);\n                    return;\n                }\n\n                mStatus = FileDownloadStatus.toFileDownloadService;\n            }\n\n            FileDownloadList.getImpl().add(runningTask);\n            if (FileDownloadHelper.inspectAndInflowDownloaded(\n                    origin.getId(), origin.getTargetFilePath(), origin.isForceReDownload(), true)\n                    ) {\n                // Will be removed when the complete message is received in #update\n                return;\n            }\n\n            final boolean succeed = FileDownloadServiceProxy.getImpl().\n                    start(\n                            origin.getUrl(),\n                            origin.getPath(),\n                            origin.isPathAsDirectory(),\n                            origin.getCallbackProgressTimes(),\n                            origin.getCallbackProgressMinInterval(),\n                            origin.getAutoRetryTimes(),\n                            origin.isForceReDownload(),\n                            mTask.getHeader(),\n                            origin.isWifiRequired());\n\n            if (mStatus == FileDownloadStatus.paused) {\n                FileDownloadLog.w(this, \"High concurrent cause, this task %d will be paused,\"\n                                + \"because of the status is paused, so the pause action must be \"\n                                + \"applied\",\n                        getId());\n                if (succeed) {\n                    FileDownloadServiceProxy.getImpl().pause(getId());\n                }\n                return;\n            }\n\n            if (!succeed) {\n                //noinspection StatementWithEmptyBody\n                if (!lostConnectedHandler.dispatchTaskStart(runningTask)) {\n                    final MessageSnapshot snapshot = prepareErrorMessage(\n                            new RuntimeException(\"Occur Unknown Error, when request to start\"\n                                    + \" maybe some problem in binder, maybe the process was killed \"\n                                    + \"in unexpected.\"));\n\n                    if (FileDownloadList.getImpl().isNotContains(runningTask)) {\n                        lostConnectedHandler.taskWorkFine(runningTask);\n                        FileDownloadList.getImpl().add(runningTask);\n                    }\n\n                    FileDownloadList.getImpl().remove(runningTask, snapshot);\n\n                } else {\n                    // the FileDownload Service host process was killed when request stating and it\n                    // will be restarted by LostServiceConnectedHandler.\n                }\n            } else {\n                lostConnectedHandler.taskWorkFine(runningTask);\n            }\n\n        } catch (Throwable e) {\n            e.printStackTrace();\n\n            FileDownloadList.getImpl().remove(runningTask, prepareErrorMessage(e));\n        }\n    }\n\n    @Override\n    public boolean equalListener(FileDownloadListener listener) {\n        return mTask.getRunningTask().getOrigin().getListener() == listener;\n    }\n}\n"
  },
  {
    "path": "library/src/main/java/com/liulishuo/filedownloader/FileDownloadConnectListener.java",
    "content": "/*\n * Copyright (c) 2015 LingoChamp Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *    http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.liulishuo.filedownloader;\n\nimport com.liulishuo.filedownloader.event.DownloadServiceConnectChangedEvent;\nimport com.liulishuo.filedownloader.event.IDownloadEvent;\nimport com.liulishuo.filedownloader.event.IDownloadListener;\n\n/**\n * The listener for listening whether the service establishes connection or disconnected.\n *\n * @see com.liulishuo.filedownloader.services.BaseFileServiceUIGuard#releaseConnect(boolean)\n */\npublic abstract class FileDownloadConnectListener extends IDownloadListener {\n\n    private DownloadServiceConnectChangedEvent.ConnectStatus mConnectStatus;\n\n    public FileDownloadConnectListener() {\n    }\n\n    @Override\n    public boolean callback(IDownloadEvent event) {\n        if (event instanceof DownloadServiceConnectChangedEvent) {\n            final DownloadServiceConnectChangedEvent connectChangedEvent\n                    = (DownloadServiceConnectChangedEvent) event;\n            mConnectStatus = connectChangedEvent.getStatus();\n\n            if (mConnectStatus == DownloadServiceConnectChangedEvent.ConnectStatus.connected) {\n                connected();\n            } else {\n                disconnected();\n            }\n        }\n        return false;\n    }\n\n    /**\n     * connected file download service\n     */\n    public abstract void connected();\n\n    /**\n     * disconnected file download service\n     */\n    public abstract void disconnected();\n\n    public DownloadServiceConnectChangedEvent.ConnectStatus getConnectStatus() {\n        return mConnectStatus;\n    }\n}\n"
  },
  {
    "path": "library/src/main/java/com/liulishuo/filedownloader/FileDownloadEventPool.java",
    "content": "/*\n * Copyright (c) 2015 LingoChamp Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *    http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.liulishuo.filedownloader;\n\nimport com.liulishuo.filedownloader.event.DownloadEventPoolImpl;\n\n/**\n * The event pool.\n */\npublic class FileDownloadEventPool extends DownloadEventPoolImpl {\n\n    private static class HolderClass {\n        private static final FileDownloadEventPool INSTANCE = new FileDownloadEventPool();\n    }\n\n    private FileDownloadEventPool() {\n        super();\n    }\n\n    public static FileDownloadEventPool getImpl() {\n        return HolderClass.INSTANCE;\n    }\n\n}\n"
  },
  {
    "path": "library/src/main/java/com/liulishuo/filedownloader/FileDownloadLargeFileListener.java",
    "content": "/*\n * Copyright (c) 2015 LingoChamp Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *    http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.liulishuo.filedownloader;\n\n/**\n * The listener for listening the downloading status changing.\n * <p>\n * This listener will be used when the file size of the task is greater than 1.99G.\n */\n@SuppressWarnings({\"WeakerAccess\", \"UnusedParameters\"})\npublic abstract class FileDownloadLargeFileListener extends FileDownloadListener {\n\n    public FileDownloadLargeFileListener() {\n    }\n\n    /**\n     * @see #FileDownloadLargeFileListener()\n     * @deprecated not handle priority any more\n     */\n    public FileDownloadLargeFileListener(int priority) {\n        //noinspection deprecation\n        super(priority);\n    }\n\n    /**\n     * Entry queue, and pending\n     *\n     * @param task       Current task\n     * @param soFarBytes Already downloaded bytes stored in the db\n     * @param totalBytes Total bytes stored in the db\n     */\n    protected abstract void pending(final BaseDownloadTask task, final long soFarBytes,\n                                    final long totalBytes);\n\n    /**\n     * @param task       The task\n     * @param soFarBytes Already downloaded bytes stored in the db\n     * @param totalBytes Total bytes stored in the db\n     * @deprecated replaced with {@link #pending(BaseDownloadTask, long, long)}\n     */\n    @Override\n    protected void pending(BaseDownloadTask task, int soFarBytes, int totalBytes) {\n    }\n\n    /**\n     * Connected\n     *\n     * @param task       Current task\n     * @param etag       ETag\n     * @param isContinue Is resume from breakpoint\n     * @param soFarBytes Number of bytes download so far\n     * @param totalBytes Total size of the download in bytes\n     */\n    @SuppressWarnings(\"EmptyMethod\")\n    protected void connected(final BaseDownloadTask task, final String etag,\n                             final boolean isContinue,\n                             final long soFarBytes, final long totalBytes) {\n    }\n\n    /**\n     * @param task       The task\n     * @param etag       ETag\n     * @param isContinue Is resume from breakpoint\n     * @param soFarBytes Number of bytes download so far\n     * @param totalBytes Total size of the download in bytes\n     * @deprecated replaced with {@link #connected(BaseDownloadTask, String, boolean, long, long)}\n     */\n    @Override\n    protected void connected(BaseDownloadTask task, String etag, boolean isContinue, int soFarBytes,\n                             int totalBytes) {\n    }\n\n    /**\n     * @param task       Current task\n     * @param soFarBytes Number of bytes download so far\n     * @param totalBytes Total size of the download in bytes\n     */\n    protected abstract void progress(final BaseDownloadTask task, final long soFarBytes,\n                                     final long totalBytes);\n\n    /**\n     * @param task       The task\n     * @param soFarBytes Number of bytes download so far\n     * @param totalBytes Total size of the download in bytes\n     * @deprecated replaced with {@link #progress(BaseDownloadTask, long, long)}\n     */\n    @Override\n    protected void progress(BaseDownloadTask task, int soFarBytes, int totalBytes) {\n    }\n\n    /**\n     * Start Retry\n     *\n     * @param task          Current task\n     * @param ex            why retry\n     * @param retryingTimes How many times will retry\n     * @param soFarBytes    Number of bytes download so far\n     */\n    @SuppressWarnings(\"EmptyMethod\")\n    protected void retry(final BaseDownloadTask task, final Throwable ex,\n                         final int retryingTimes, final long soFarBytes) {\n    }\n\n    /**\n     * @param task          The task\n     * @param ex            Why retry\n     * @param retryingTimes How many times will retry\n     * @param soFarBytes    Number of bytes download so far\n     * @deprecated replaced with {@link #retry(BaseDownloadTask, Throwable, int, long)}\n     */\n    @SuppressWarnings(\"EmptyMethod\")\n    @Override\n    protected void retry(BaseDownloadTask task, Throwable ex, int retryingTimes, int soFarBytes) {\n    }\n\n    /**\n     * Download paused\n     *\n     * @param task       Current task\n     * @param soFarBytes Number of bytes download so far\n     * @param totalBytes Total size of the download in bytes\n     */\n    protected abstract void paused(final BaseDownloadTask task, final long soFarBytes,\n                                   final long totalBytes);\n\n    /**\n     * @param task       The task\n     * @param soFarBytes Number of bytes download so far\n     * @param totalBytes Total size of the download in bytes\n     * @deprecated replaced with {@link #paused(BaseDownloadTask, long, long)}\n     */\n    @Override\n    protected void paused(BaseDownloadTask task, int soFarBytes, int totalBytes) {\n    }\n}\n"
  },
  {
    "path": "library/src/main/java/com/liulishuo/filedownloader/FileDownloadLine.java",
    "content": "/*\n * Copyright (c) 2015 LingoChamp Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *   http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.liulishuo.filedownloader;\n\nimport android.app.Notification;\nimport android.os.Looper;\n\nimport com.liulishuo.filedownloader.model.FileDownloadStatus;\n\nimport java.io.File;\n\n/**\n * The FileDownload synchronous line.\n *\n * @see FileDownloader#insureServiceBind()\n * @see #wait(ConnectSubscriber)\n */\n\npublic class FileDownloadLine {\n\n    /**\n     * The {@link FileDownloader#startForeground(int, Notification)} request.\n     */\n    public void startForeground(final int id, final Notification notification) {\n        if (FileDownloader.getImpl().isServiceConnected()) {\n            FileDownloader.getImpl().startForeground(id, notification);\n            return;\n        }\n\n        final ConnectSubscriber subscriber = new ConnectSubscriber() {\n            @Override\n            public void connected() {\n                FileDownloader.getImpl().startForeground(id, notification);\n            }\n\n            @Override\n            public Object getValue() {\n                return null;\n            }\n        };\n\n        wait(subscriber);\n    }\n\n    /**\n     * The {@link FileDownloader#getSoFar(int)} request.\n     */\n    public long getSoFar(final int id) {\n        if (FileDownloader.getImpl().isServiceConnected()) {\n            return FileDownloader.getImpl().getSoFar(id);\n        }\n\n        final ConnectSubscriber subscriber = new ConnectSubscriber() {\n            private long mValue;\n\n            @Override\n            public void connected() {\n                mValue = FileDownloader.getImpl().getSoFar(id);\n            }\n\n            @Override\n            public Object getValue() {\n                return mValue;\n            }\n        };\n\n        wait(subscriber);\n\n        return (long) subscriber.getValue();\n    }\n\n    /**\n     * The {@link FileDownloader#getTotal(int)} request.\n     */\n    public long getTotal(final int id) {\n        if (FileDownloader.getImpl().isServiceConnected()) {\n            return FileDownloader.getImpl().getTotal(id);\n        }\n\n        final ConnectSubscriber subscriber = new ConnectSubscriber() {\n            private long mValue;\n\n            @Override\n            public void connected() {\n                mValue = FileDownloader.getImpl().getTotal(id);\n            }\n\n            @Override\n            public Object getValue() {\n                return mValue;\n            }\n        };\n\n        wait(subscriber);\n\n        return (long) subscriber.getValue();\n    }\n\n    /**\n     * The {@link FileDownloader#getStatus(int, String)} request.\n     */\n    public byte getStatus(final int id, final String path) {\n        if (FileDownloader.getImpl().isServiceConnected()) {\n            return FileDownloader.getImpl().getStatus(id, path);\n        }\n\n        if (path != null && new File(path).exists()) {\n            return FileDownloadStatus.completed;\n        }\n\n        final ConnectSubscriber subscriber = new ConnectSubscriber() {\n            private byte mValue;\n\n            @Override\n            public void connected() {\n                mValue = FileDownloader.getImpl().getStatus(id, path);\n            }\n\n            @Override\n            public Object getValue() {\n                return mValue;\n            }\n        };\n\n        wait(subscriber);\n\n        return (byte) subscriber.getValue();\n    }\n\n    private void wait(final ConnectSubscriber subscriber) {\n        final ConnectListener connectListener = new ConnectListener(subscriber);\n\n        //noinspection SynchronizationOnLocalVariableOrMethodParameter\n        synchronized (connectListener) {\n            FileDownloader.getImpl().bindService(connectListener);\n\n            if (!connectListener.isFinished()) {\n\n                if (Thread.currentThread() == Looper.getMainLooper().getThread()) {\n                    throw new IllegalThreadStateException(\"Sorry, FileDownloader can not block the \"\n                            + \"main thread, because the system is also  callbacks \"\n                            + \"ServiceConnection#onServiceConnected method in the main thread.\");\n                }\n\n                try {\n                    connectListener.wait(200 * 1000);\n                } catch (InterruptedException e) {\n                    e.printStackTrace();\n                }\n            }\n        }\n    }\n\n    static class ConnectListener implements Runnable {\n        private boolean mIsFinished = false;\n        private final ConnectSubscriber mSubscriber;\n\n        ConnectListener(ConnectSubscriber subscriber) {\n            this.mSubscriber = subscriber;\n        }\n\n        public boolean isFinished() {\n            return mIsFinished;\n        }\n\n        @Override\n        public void run() {\n            synchronized (this) {\n                mSubscriber.connected();\n                mIsFinished = true;\n                notifyAll();\n            }\n        }\n    }\n\n    interface ConnectSubscriber {\n        void connected();\n\n        Object getValue();\n    }\n}\n"
  },
  {
    "path": "library/src/main/java/com/liulishuo/filedownloader/FileDownloadLineAsync.java",
    "content": "/*\n * Copyright (c) 2015 LingoChamp Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *   http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.liulishuo.filedownloader;\n\nimport android.app.Notification;\n\n/**\n * The FileDownloader asynchronous line.\n */\n\npublic class FileDownloadLineAsync {\n\n    /**\n     * The {@link FileDownloader#startForeground(int, Notification)} request.\n     */\n    public boolean startForeground(final int id, final Notification notification) {\n        if (FileDownloader.getImpl().isServiceConnected()) {\n            FileDownloader.getImpl().startForeground(id, notification);\n            return true;\n        } else {\n            FileDownloader.getImpl().bindService(new Runnable() {\n                @Override\n                public void run() {\n                    FileDownloader.getImpl().startForeground(id, notification);\n                }\n            });\n            return false;\n        }\n    }\n\n}\n"
  },
  {
    "path": "library/src/main/java/com/liulishuo/filedownloader/FileDownloadList.java",
    "content": "/*\n * Copyright (c) 2015 LingoChamp Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *    http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.liulishuo.filedownloader;\n\nimport com.liulishuo.filedownloader.message.MessageSnapshot;\nimport com.liulishuo.filedownloader.message.MessageSnapshotTaker;\nimport com.liulishuo.filedownloader.model.FileDownloadStatus;\nimport com.liulishuo.filedownloader.util.FileDownloadLog;\n\nimport java.util.ArrayList;\nimport java.util.List;\n\n/**\n * Storing all tasks in processing in the Main-Process.\n */\n@SuppressWarnings(\"UnusedReturnValue\")\npublic class FileDownloadList {\n\n    private static final class HolderClass {\n        private static final FileDownloadList INSTANCE = new FileDownloadList();\n    }\n\n    public static FileDownloadList getImpl() {\n        return HolderClass.INSTANCE;\n    }\n\n    private final ArrayList<BaseDownloadTask.IRunningTask> mList;\n\n    private FileDownloadList() {\n        mList = new ArrayList<>();\n    }\n\n    boolean isEmpty() {\n        return mList.isEmpty();\n    }\n\n    int size() {\n        return mList.size();\n    }\n\n    /**\n     * @param id download id\n     * @return get counts os same id\n     */\n    int count(final int id) {\n        int size = 0;\n        synchronized (mList) {\n            for (BaseDownloadTask.IRunningTask task : mList) {\n                if (task.is(id)) {\n                    size++;\n                }\n            }\n        }\n        return size;\n    }\n\n    public BaseDownloadTask.IRunningTask get(final int id) {\n        synchronized (mList) {\n            for (BaseDownloadTask.IRunningTask task : mList) {\n                // when FileDownloadMgr#isDownloading\n                if (task.is(id)) {\n                    return task;\n                }\n            }\n        }\n        return null;\n    }\n\n    List<BaseDownloadTask.IRunningTask> getReceiveServiceTaskList(final int id) {\n        final List<BaseDownloadTask.IRunningTask> list = new ArrayList<>();\n        synchronized (this.mList) {\n            for (BaseDownloadTask.IRunningTask task : this.mList) {\n                if (task.is(id) && !task.isOver()) {\n\n                    final byte status = task.getOrigin().getStatus();\n                    if (status != FileDownloadStatus.INVALID_STATUS\n                            && status != FileDownloadStatus.toLaunchPool) {\n                        list.add(task);\n                    }\n                }\n            }\n        }\n\n        return list;\n    }\n\n    List<BaseDownloadTask.IRunningTask> getDownloadingList(final int id) {\n        final List<BaseDownloadTask.IRunningTask> list = new ArrayList<>();\n        synchronized (this.mList) {\n            for (BaseDownloadTask.IRunningTask task : this.mList) {\n                if (task.is(id)\n                        && !task.isOver()) {\n                    list.add(task);\n                }\n            }\n        }\n\n        return list;\n    }\n\n    boolean isNotContains(final BaseDownloadTask.IRunningTask download) {\n        return mList.isEmpty() || !mList.contains(download);\n    }\n\n    List<BaseDownloadTask.IRunningTask> copy(final FileDownloadListener listener) {\n        final List<BaseDownloadTask.IRunningTask> targetList = new ArrayList<>();\n        synchronized (mList) {\n            // Prevent size changing\n            for (BaseDownloadTask.IRunningTask task : mList) {\n                if (task.is(listener)) {\n                    targetList.add(task);\n                }\n            }\n            return targetList;\n        }\n    }\n\n    List<BaseDownloadTask.IRunningTask> assembleTasksToStart(int attachKey,\n                                                             FileDownloadListener listener) {\n        final List<BaseDownloadTask.IRunningTask> targetList = new ArrayList<>();\n        synchronized (mList) {\n            // Prevent size changing\n            for (BaseDownloadTask.IRunningTask task : mList) {\n                if (task.getOrigin().getListener() == listener && !task.getOrigin().isAttached()) {\n                    task.setAttachKeyByQueue(attachKey);\n                    targetList.add(task);\n                }\n            }\n            return targetList;\n        }\n    }\n\n    BaseDownloadTask.IRunningTask[] copy() {\n        synchronized (mList) {\n            // Prevent size changing\n            BaseDownloadTask.IRunningTask[] copy = new BaseDownloadTask.IRunningTask[mList.size()];\n            return mList.toArray(copy);\n        }\n    }\n\n    /**\n     * Divert all data in list 2 destination list\n     */\n    void divertAndIgnoreDuplicate(\n            @SuppressWarnings(\"SameParameterValue\") final List<BaseDownloadTask.IRunningTask>\n                    destination) {\n        synchronized (mList) {\n            for (BaseDownloadTask.IRunningTask iRunningTask : mList) {\n                if (!destination.contains(iRunningTask)) {\n                    destination.add(iRunningTask);\n                }\n            }\n            mList.clear();\n        }\n    }\n\n    /**\n     * @param willRemoveDownload will be remove\n     */\n    public boolean remove(final BaseDownloadTask.IRunningTask willRemoveDownload,\n                          MessageSnapshot snapshot) {\n        final byte removeByStatus = snapshot.getStatus();\n        boolean succeed;\n        synchronized (mList) {\n            succeed = mList.remove(willRemoveDownload);\n            if (succeed && mList.size() == 0) {\n                if (FileDownloadServiceProxy.getImpl().isRunServiceForeground()) {\n                    FileDownloader.getImpl().stopForeground(true);\n                }\n            }\n        }\n        if (FileDownloadLog.NEED_LOG) {\n            if (mList.size() == 0) {\n                FileDownloadLog.v(this, \"remove %s left %d %d\",\n                        willRemoveDownload, removeByStatus, mList.size());\n            }\n        }\n\n        if (succeed) {\n            final IFileDownloadMessenger messenger = willRemoveDownload.getMessageHandler().\n                    getMessenger();\n            // Notify 2 Listener\n            switch (removeByStatus) {\n                case FileDownloadStatus.warn:\n                    messenger.notifyWarn(snapshot);\n                    break;\n                case FileDownloadStatus.error:\n                    messenger.notifyError(snapshot);\n                    break;\n                case FileDownloadStatus.paused:\n                    messenger.notifyPaused(snapshot);\n                    break;\n                case FileDownloadStatus.completed:\n                    messenger\n                            .notifyBlockComplete(MessageSnapshotTaker.takeBlockCompleted(snapshot));\n                    break;\n                default:\n                    // ignored\n            }\n        } else {\n            FileDownloadLog.e(this, \"remove error, not exist: %s %d\", willRemoveDownload,\n                    removeByStatus);\n        }\n\n        return succeed;\n    }\n\n    void add(final BaseDownloadTask.IRunningTask task) {\n        if (!task.getOrigin().isAttached()) {\n            // if this task didn't attach to any key, this task must be an isolated task, so we\n            // generate a key and attache it to this task, make sure this task not be assembled by\n            // a queue.\n            task.setAttachKeyDefault();\n        }\n\n        if (task.getMessageHandler().getMessenger().notifyBegin()) {\n            addUnchecked(task);\n        }\n    }\n\n    /**\n     * This method generally used for enqueuing the task which will be assembled by a queue.\n     *\n     * @see BaseDownloadTask.InQueueTask#enqueue()\n     */\n    void addUnchecked(final BaseDownloadTask.IRunningTask task) {\n        if (task.isMarkedAdded2List()) {\n            return;\n        }\n\n        synchronized (mList) {\n            if (mList.contains(task)) {\n                FileDownloadLog.w(this, \"already has %s\", task);\n            } else {\n                task.markAdded2List();\n                mList.add(task);\n                if (FileDownloadLog.NEED_LOG) {\n                    FileDownloadLog.v(this, \"add list in all %s %d %d\", task,\n                            task.getOrigin().getStatus(), mList.size());\n                }\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "library/src/main/java/com/liulishuo/filedownloader/FileDownloadListener.java",
    "content": "/*\n * Copyright (c) 2015 LingoChamp Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *    http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.liulishuo.filedownloader;\n\n\nimport com.liulishuo.filedownloader.notification.FileDownloadNotificationListener;\nimport com.liulishuo.filedownloader.util.FileDownloadLog;\n\n/**\n * Normally flow: {@link #pending} -> {@link #started} -> {@link #connected} -> {@link #progress}\n * -> {@link #blockComplete} -> {@link #completed}\n * <p/>\n * Maybe over with: {@link #paused}/{@link #completed}/{@link #error}/{@link #warn}\n * <p/>\n * If the task has already downloaded and exist, you will only receive follow callbacks:\n * {@link #blockComplete} ->{@link #completed}\n *\n * @see FileDownloadLargeFileListener\n * @see FileDownloadNotificationListener\n * @see BaseDownloadTask#setSyncCallback(boolean)\n */\n@SuppressWarnings({\"WeakerAccess\", \"UnusedParameters\"})\npublic abstract class FileDownloadListener {\n\n    public FileDownloadListener() {\n    }\n\n    /**\n     * @param priority not handle priority any more\n     * @deprecated not handle priority any more\n     */\n    public FileDownloadListener(int priority) {\n        FileDownloadLog.w(this, \"not handle priority any more\");\n    }\n\n    /**\n     * Whether this listener has already invalidated to receive callbacks.\n     *\n     * @return {@code true} If you don't want to receive any callbacks for this listener.\n     */\n    protected boolean isInvalid() {\n        return false;\n    }\n\n    /**\n     * Enqueue, and pending, waiting for {@link #started(BaseDownloadTask)}.\n     *\n     * @param task       The task\n     * @param soFarBytes Already downloaded and reusable bytes stored in the db\n     * @param totalBytes Total bytes stored in the db\n     * @see IFileDownloadMessenger#notifyPending\n     */\n    protected abstract void pending(final BaseDownloadTask task, final int soFarBytes,\n                                    final int totalBytes);\n\n    /**\n     * Finish pending, and start the download runnable.\n     *\n     * @param task Current task.\n     * @see IFileDownloadMessenger#notifyStarted\n     */\n    protected void started(final BaseDownloadTask task) {\n    }\n\n    /**\n     * Already connected to the server, and received the Http-response.\n     *\n     * @param task       The task\n     * @param etag       ETag\n     * @param isContinue Is resume from breakpoint\n     * @param soFarBytes Number of bytes download so far\n     * @param totalBytes Total size of the download in bytes\n     * @see IFileDownloadMessenger#notifyConnected\n     */\n    protected void connected(final BaseDownloadTask task, final String etag,\n                             final boolean isContinue, final int soFarBytes, final int totalBytes) {\n\n    }\n\n    /**\n     * Fetching datum from network and Writing to the local disk.\n     *\n     * @param task       The task\n     * @param soFarBytes Number of bytes download so far\n     * @param totalBytes Total size of the download in bytes\n     * @see IFileDownloadMessenger#notifyProgress\n     */\n    protected abstract void progress(final BaseDownloadTask task, final int soFarBytes,\n                                     final int totalBytes);\n\n    /**\n     * Unlike other methods in {@link #FileDownloadListener}, BlockComplete is executed in other\n     * thread than main as default, when you receive this execution, it means has already completed\n     * downloading, but just block the execution of {@link #completed(BaseDownloadTask)}. therefore,\n     * you can unzip or do some ending operation before {@link #completed(BaseDownloadTask)} in\n     * other threads.\n     *\n     * @param task the current task\n     * @throws Throwable if any {@code throwable} is thrown in this method, you will receive the\n     *                   callback method of {@link #error(BaseDownloadTask, Throwable)} with the\n     *                   {@code throwable} parameter instead of {@link #completed(BaseDownloadTask)}\n     * @see IFileDownloadMessenger#notifyBlockComplete\n     */\n    protected void blockComplete(final BaseDownloadTask task) throws Throwable {\n    }\n\n    /**\n     * Occur a exception and has chance{@link BaseDownloadTask#setAutoRetryTimes(int)} to retry and\n     * start Retry.\n     *\n     * @param task          The task\n     * @param ex            Why retry\n     * @param retryingTimes How many times will retry\n     * @param soFarBytes    Number of bytes download so far\n     * @see IFileDownloadMessenger#notifyRetry\n     */\n    protected void retry(final BaseDownloadTask task, final Throwable ex, final int retryingTimes,\n                         final int soFarBytes) {\n    }\n\n    // ======================= The task is over, if execute below methods =======================\n\n    /**\n     * Achieve complete ceremony.\n     * <p/>\n     * Complete downloading.\n     *\n     * @param task The task\n     * @see IFileDownloadMessenger#notifyCompleted\n     * @see #blockComplete(BaseDownloadTask)\n     */\n    protected abstract void completed(final BaseDownloadTask task);\n\n    /**\n     * Task is paused, the vast majority of cases is invoking the {@link BaseDownloadTask#pause()}\n     * manually.\n     *\n     * @param task       The task\n     * @param soFarBytes Number of bytes download so far\n     * @param totalBytes Total size of the download in bytes\n     * @see IFileDownloadMessenger#notifyPaused\n     */\n    protected abstract void paused(final BaseDownloadTask task, final int soFarBytes,\n                                   final int totalBytes);\n\n    /**\n     * Occur a exception, but don't has any chance to retry.\n     *\n     * @param task The task\n     * @param e    Any throwable on download pipeline\n     * @see IFileDownloadMessenger#notifyError(com.liulishuo.filedownloader.message.MessageSnapshot)\n     * @see com.liulishuo.filedownloader.exception.FileDownloadHttpException\n     * @see com.liulishuo.filedownloader.exception.FileDownloadGiveUpRetryException\n     * @see com.liulishuo.filedownloader.exception.FileDownloadOutOfSpaceException\n     */\n    protected abstract void error(final BaseDownloadTask task, final Throwable e);\n\n    /**\n     * There has already had some same Tasks(Same-URL & Same-SavePath) in Pending-Queue or is\n     * running.\n     *\n     * @param task The task\n     * @see IFileDownloadMessenger#notifyWarn(com.liulishuo.filedownloader.message.MessageSnapshot)\n     */\n    protected abstract void warn(final BaseDownloadTask task);\n\n}\n"
  },
  {
    "path": "library/src/main/java/com/liulishuo/filedownloader/FileDownloadMessageStation.java",
    "content": "/*\n * Copyright (c) 2015 LingoChamp Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *    http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.liulishuo.filedownloader;\n\nimport android.os.Handler;\nimport android.os.Looper;\nimport android.os.Message;\n\nimport com.liulishuo.filedownloader.util.FileDownloadExecutors;\n\nimport java.util.ArrayList;\nimport java.util.concurrent.Executor;\nimport java.util.concurrent.LinkedBlockingQueue;\n\n/**\n * The message station to transfer task events to {@link FileDownloadListener}.\n */\n@SuppressWarnings(\"WeakerAccess\")\npublic class FileDownloadMessageStation {\n\n    private static final Executor BLOCK_COMPLETED_POOL = FileDownloadExecutors.\n            newDefaultThreadPool(5, \"BlockCompleted\");\n\n    private final Handler handler;\n    private final LinkedBlockingQueue<IFileDownloadMessenger> waitingQueue;\n\n    private static final class HolderClass {\n        private static final FileDownloadMessageStation INSTANCE = new FileDownloadMessageStation();\n    }\n\n    public static FileDownloadMessageStation getImpl() {\n        return HolderClass.INSTANCE;\n    }\n\n    private FileDownloadMessageStation() {\n        handler = new Handler(Looper.getMainLooper(), new UIHandlerCallback());\n        waitingQueue = new LinkedBlockingQueue<>();\n    }\n\n    void requestEnqueue(final IFileDownloadMessenger messenger) {\n        requestEnqueue(messenger, false);\n    }\n\n    void requestEnqueue(final IFileDownloadMessenger messenger,\n                        @SuppressWarnings(\"SameParameterValue\") boolean immediately) {\n\n        if (messenger.handoverDirectly()) {\n            messenger.handoverMessage();\n            return;\n        }\n\n        if (interceptBlockCompleteMessage(messenger)) {\n            return;\n        }\n\n        if (!isIntervalValid()) {\n            // invalid\n            // clear all waiting queue.\n            if (!waitingQueue.isEmpty()) {\n                synchronized (queueLock) {\n                    if (!waitingQueue.isEmpty()) {\n                        for (IFileDownloadMessenger iFileDownloadMessenger : waitingQueue) {\n                            handoverInUIThread(iFileDownloadMessenger);\n                        }\n                    }\n                    waitingQueue.clear();\n                }\n            }\n        }\n\n        if (!isIntervalValid() || immediately) {\n            // post to UI thread immediately.\n            handoverInUIThread(messenger);\n            return;\n        }\n\n        // enqueue.\n        enqueue(messenger);\n    }\n\n    private void handoverInUIThread(IFileDownloadMessenger messenger) {\n        handler.sendMessage(handler.obtainMessage(HANDOVER_A_MESSENGER, messenger));\n    }\n\n    /**\n     * @param messenger {@link com.liulishuo.filedownloader.IFileDownloadMessenger}\n     * @return true if the messenger is a block complete messenger and handle it through\n     * {@linkplain FileDownloadMessageStation#BLOCK_COMPLETED_POOL} in the meantime.\n     */\n    private static boolean interceptBlockCompleteMessage(final IFileDownloadMessenger messenger) {\n        if (messenger.isBlockingCompleted()) {\n            BLOCK_COMPLETED_POOL.execute(new Runnable() {\n                @Override\n                public void run() {\n                    messenger.handoverMessage();\n                }\n            });\n            return true;\n        } else {\n            return false;\n        }\n    }\n\n    private final Object queueLock = new Object();\n\n    private void enqueue(final IFileDownloadMessenger messenger) {\n        synchronized (queueLock) {\n            waitingQueue.offer(messenger);\n        }\n\n        push();\n    }\n\n    private void push() {\n\n        final int delayMillis;\n        synchronized (queueLock) {\n            if (!disposingList.isEmpty()) {\n                // is disposing.\n                return;\n            }\n\n            if (waitingQueue.isEmpty()) {\n                // not messenger need be handled.\n                return;\n            }\n\n            if (!isIntervalValid()) {\n                waitingQueue.drainTo(disposingList);\n                delayMillis = 0;\n            } else {\n                delayMillis = INTERVAL;\n                final int size = Math.min(waitingQueue.size(), SUB_PACKAGE_SIZE);\n                for (int i = 0; i < size; i++) {\n                    disposingList.add(waitingQueue.remove());\n                }\n            }\n\n\n        }\n\n        handler.sendMessageDelayed(handler.obtainMessage(DISPOSE_MESSENGER_LIST, disposingList),\n                delayMillis);\n    }\n\n\n    static final int HANDOVER_A_MESSENGER = 1;\n    static final int DISPOSE_MESSENGER_LIST = 2;\n    private final ArrayList<IFileDownloadMessenger> disposingList = new ArrayList<>();\n\n    private static class UIHandlerCallback implements Handler.Callback {\n\n        @Override\n        public boolean handleMessage(Message msg) {\n            if (msg.what == HANDOVER_A_MESSENGER) {\n                ((IFileDownloadMessenger) msg.obj).handoverMessage();\n            } else if (msg.what == DISPOSE_MESSENGER_LIST) {\n                //noinspection unchecked\n                dispose((ArrayList<IFileDownloadMessenger>) msg.obj);\n                FileDownloadMessageStation.getImpl().push();\n            }\n            return true;\n        }\n\n        private void dispose(final ArrayList<IFileDownloadMessenger> disposingList) {\n            // dispose Sub-package-size each time.\n            for (IFileDownloadMessenger iFileDownloadMessenger : disposingList) {\n                if (interceptBlockCompleteMessage(iFileDownloadMessenger)) {\n                    continue;\n                }\n                iFileDownloadMessenger.handoverMessage();\n            }\n\n            disposingList.clear();\n        }\n    }\n\n\n    // ----------------- WAIT AND FREE INTERNAL TECH，SPLIT AND SUB-PACKAGE TECH ----------------\n\n    public static final int DEFAULT_INTERVAL = 10;\n    public static final int DEFAULT_SUB_PACKAGE_SIZE = 5;\n\n    /**\n     * For avoid dropped ui refresh frame.\n     * <p/>\n     * Every {@code INTERVAL} milliseconds post 1 message to the ui thread at most,\n     * and will handle up to {@link FileDownloadMessageStation#SUB_PACKAGE_SIZE} events on the ui\n     * thread at most.\n     */\n    static int INTERVAL = DEFAULT_INTERVAL;\n    // the size of one package for callback on the ui thread.\n    static int SUB_PACKAGE_SIZE = DEFAULT_SUB_PACKAGE_SIZE;\n\n    public static boolean isIntervalValid() {\n        return INTERVAL > 0;\n    }\n}\n"
  },
  {
    "path": "library/src/main/java/com/liulishuo/filedownloader/FileDownloadMessenger.java",
    "content": "/*\n * Copyright (c) 2015 LingoChamp Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *    http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.liulishuo.filedownloader;\n\nimport com.liulishuo.filedownloader.message.BlockCompleteMessage;\nimport com.liulishuo.filedownloader.message.MessageSnapshot;\nimport com.liulishuo.filedownloader.model.FileDownloadStatus;\nimport com.liulishuo.filedownloader.util.FileDownloadLog;\nimport com.liulishuo.filedownloader.util.FileDownloadUtils;\n\nimport java.util.Queue;\nimport java.util.concurrent.LinkedBlockingQueue;\n\n/**\n * The messenger for sending messages to  {@link FileDownloadListener}.\n *\n * @see IFileDownloadMessenger\n */\nclass FileDownloadMessenger implements IFileDownloadMessenger {\n\n    private BaseDownloadTask.IRunningTask mTask;\n    private BaseDownloadTask.LifeCycleCallback mLifeCycleCallback;\n\n    private Queue<MessageSnapshot> parcelQueue;\n\n    private boolean mIsDiscard = false;\n\n    FileDownloadMessenger(final BaseDownloadTask.IRunningTask task,\n                          final BaseDownloadTask.LifeCycleCallback callback) {\n        init(task, callback);\n    }\n\n    private void init(final BaseDownloadTask.IRunningTask task,\n                      BaseDownloadTask.LifeCycleCallback callback) {\n        this.mTask = task;\n        this.mLifeCycleCallback = callback;\n        parcelQueue = new LinkedBlockingQueue<>();\n    }\n\n    @Override\n    public boolean notifyBegin() {\n        if (FileDownloadLog.NEED_LOG) {\n            FileDownloadLog.d(this, \"notify begin %s\", mTask);\n        }\n\n        if (mTask == null) {\n            FileDownloadLog.w(this, \"can't begin the task, the holder fo the messenger is nil, %d\",\n                    parcelQueue.size());\n            return false;\n        }\n\n        mLifeCycleCallback.onBegin();\n\n        return true;\n    }\n\n    @Override\n    public void notifyPending(MessageSnapshot snapshot) {\n        if (FileDownloadLog.NEED_LOG) {\n            FileDownloadLog.d(this, \"notify pending %s\", mTask);\n        }\n\n        mLifeCycleCallback.onIng();\n\n        process(snapshot);\n    }\n\n    @Override\n    public void notifyStarted(MessageSnapshot snapshot) {\n        if (FileDownloadLog.NEED_LOG) {\n            FileDownloadLog.d(this, \"notify started %s\", mTask);\n        }\n\n        mLifeCycleCallback.onIng();\n\n        process(snapshot);\n    }\n\n    @Override\n    public void notifyConnected(MessageSnapshot snapshot) {\n        if (FileDownloadLog.NEED_LOG) {\n            FileDownloadLog.d(this, \"notify connected %s\", mTask);\n        }\n\n        mLifeCycleCallback.onIng();\n\n        process(snapshot);\n    }\n\n    @Override\n    public void notifyProgress(MessageSnapshot snapshot) {\n        final BaseDownloadTask originTask = mTask.getOrigin();\n        if (FileDownloadLog.NEED_LOG) {\n            FileDownloadLog.d(this, \"notify progress %s %d %d\",\n                    originTask, originTask.getLargeFileSoFarBytes(),\n                    originTask.getLargeFileTotalBytes());\n        }\n        if (originTask.getCallbackProgressTimes() <= 0) {\n            if (FileDownloadLog.NEED_LOG) {\n                FileDownloadLog.d(this, \"notify progress but client not request notify %s\", mTask);\n            }\n            return;\n        }\n\n        mLifeCycleCallback.onIng();\n\n        process(snapshot);\n\n    }\n\n    /**\n     * sync\n     */\n    @Override\n    public void notifyBlockComplete(MessageSnapshot snapshot) {\n        if (FileDownloadLog.NEED_LOG) {\n            FileDownloadLog.d(this, \"notify block completed %s %s\", mTask,\n                    Thread.currentThread().getName());\n        }\n\n        mLifeCycleCallback.onIng();\n\n        process(snapshot);\n    }\n\n    @Override\n    public void notifyRetry(MessageSnapshot snapshot) {\n        if (FileDownloadLog.NEED_LOG) {\n            final BaseDownloadTask originTask = mTask.getOrigin();\n            FileDownloadLog.d(this, \"notify retry %s %d %d %s\", mTask,\n                    originTask.getAutoRetryTimes(), originTask.getRetryingTimes(),\n                    originTask.getErrorCause());\n        }\n\n        mLifeCycleCallback.onIng();\n\n        process(snapshot);\n    }\n\n    // Over state, from FileDownloadList, to user -----------------------------\n    @Override\n    public void notifyWarn(MessageSnapshot snapshot) {\n        if (FileDownloadLog.NEED_LOG) {\n            FileDownloadLog.d(this, \"notify warn %s\", mTask);\n        }\n\n        mLifeCycleCallback.onOver();\n\n        process(snapshot);\n    }\n\n    @Override\n    public void notifyError(MessageSnapshot snapshot) {\n        if (FileDownloadLog.NEED_LOG) {\n            FileDownloadLog.d(this, \"notify error %s %s\", mTask, mTask.getOrigin().getErrorCause());\n        }\n\n        mLifeCycleCallback.onOver();\n\n        process(snapshot);\n    }\n\n    @Override\n    public void notifyPaused(MessageSnapshot snapshot) {\n        if (FileDownloadLog.NEED_LOG) {\n            FileDownloadLog.d(this, \"notify paused %s\", mTask);\n        }\n\n        mLifeCycleCallback.onOver();\n\n        process(snapshot);\n    }\n\n    @Override\n    public void notifyCompleted(MessageSnapshot snapshot) {\n        if (FileDownloadLog.NEED_LOG) {\n            FileDownloadLog.d(this, \"notify completed %s\", mTask);\n        }\n\n        mLifeCycleCallback.onOver();\n\n        process(snapshot);\n    }\n\n    private void process(MessageSnapshot snapshot) {\n        if (mTask == null) {\n            if (FileDownloadLog.NEED_LOG) {\n                // the most possible of occurring this case is the thread for flowing the paused\n                // message is different with others.\n                FileDownloadLog.d(this, \"occur this case, it would be the host task of this \"\n                                + \"messenger has been over(paused/warn/completed/error) on the \"\n                                + \"other thread before receiving the snapshot(id[%d], status[%d])\",\n                        snapshot.getId(), snapshot.getStatus());\n            }\n            return;\n        }\n\n        if (mIsDiscard || mTask.getOrigin().getListener() == null) {\n            if ((FileDownloadMonitor.isValid() || mTask.isContainFinishListener())\n                    && snapshot.getStatus() == FileDownloadStatus.blockComplete) {\n                // there is a FileDownloadMonitor, so we have to ensure the 'BaseDownloadTask#over'\n                // can be invoked.\n                mLifeCycleCallback.onOver();\n            }\n\n            inspectAndHandleOverStatus(snapshot.getStatus());\n        } else {\n            parcelQueue.offer(snapshot);\n\n            FileDownloadMessageStation.getImpl().requestEnqueue(this);\n        }\n    }\n\n    private void inspectAndHandleOverStatus(int status) {\n        // If this task is in the over state, try to retire this messenger.\n        if (FileDownloadStatus.isOver(status)) {\n            if (!parcelQueue.isEmpty()) {\n                final MessageSnapshot queueTopTask = parcelQueue.peek();\n                FileDownloadLog.w(this,\n                        \"the messenger[%s](with id[%d]) has already \"\n                                + \"accomplished all his job, but there still are some messages in\"\n                                + \" parcel queue[%d] queue-top-status[%d]\",\n                        this, queueTopTask.getId(), parcelQueue.size(), queueTopTask.getStatus());\n            }\n            mTask = null;\n        }\n    }\n\n    @Override\n    public void handoverMessage() {\n        if (mIsDiscard) {\n            return;\n        }\n\n        final MessageSnapshot message = parcelQueue.poll();\n        final int currentStatus = message.getStatus();\n        final BaseDownloadTask.IRunningTask task = mTask;\n\n        if (task == null) {\n            throw new IllegalArgumentException(FileDownloadUtils.formatString(\n                    \"can't handover the message, no master to receive this \"\n                            + \"message(status[%d]) size[%d]\",\n                    currentStatus, parcelQueue.size()));\n        }\n\n        final BaseDownloadTask originTask = task.getOrigin();\n\n        final FileDownloadListener listener = originTask.getListener();\n        final ITaskHunter.IMessageHandler messageHandler = task.getMessageHandler();\n\n        inspectAndHandleOverStatus(currentStatus);\n\n        if (listener == null || listener.isInvalid()) {\n            return;\n        }\n\n        if (currentStatus == FileDownloadStatus.blockComplete) {\n            try {\n                listener.blockComplete(originTask);\n                notifyCompleted(((BlockCompleteMessage) message).transmitToCompleted());\n            } catch (Throwable throwable) {\n                notifyError(messageHandler.prepareErrorMessage(throwable));\n            }\n        } else {\n            FileDownloadLargeFileListener largeFileListener = null;\n            if (listener instanceof FileDownloadLargeFileListener) {\n                largeFileListener = (FileDownloadLargeFileListener) listener;\n            }\n\n            switch (currentStatus) {\n                case FileDownloadStatus.pending:\n                    if (largeFileListener != null) {\n                        largeFileListener.pending(originTask,\n                                message.getLargeSofarBytes(),\n                                message.getLargeTotalBytes());\n                    } else {\n                        listener.pending(originTask,\n                                message.getSmallSofarBytes(),\n                                message.getSmallTotalBytes());\n                    }\n\n                    break;\n                case FileDownloadStatus.started:\n                    listener.started(originTask);\n                    break;\n                case FileDownloadStatus.connected:\n                    if (largeFileListener != null) {\n                        largeFileListener.connected(originTask,\n                                message.getEtag(),\n                                message.isResuming(),\n                                originTask.getLargeFileSoFarBytes(),\n                                message.getLargeTotalBytes());\n\n                    } else {\n                        listener.connected(originTask,\n                                message.getEtag(),\n                                message.isResuming(),\n                                originTask.getSmallFileSoFarBytes(),\n                                message.getSmallTotalBytes());\n                    }\n\n                    break;\n                case FileDownloadStatus.progress:\n                    if (largeFileListener != null) {\n                        largeFileListener.progress(originTask,\n                                message.getLargeSofarBytes(),\n                                originTask.getLargeFileTotalBytes());\n\n                    } else {\n                        listener.progress(originTask,\n                                message.getSmallSofarBytes(),\n                                originTask.getSmallFileTotalBytes());\n                    }\n                    break;\n                case FileDownloadStatus.retry:\n                    if (largeFileListener != null) {\n                        largeFileListener.retry(originTask,\n                                message.getThrowable(),\n                                message.getRetryingTimes(),\n                                message.getLargeSofarBytes());\n                    } else {\n                        listener.retry(originTask,\n                                message.getThrowable(),\n                                message.getRetryingTimes(),\n                                message.getSmallSofarBytes());\n                    }\n\n                    break;\n                case FileDownloadStatus.completed:\n                    listener.completed(originTask);\n                    break;\n                case FileDownloadStatus.error:\n                    listener.error(originTask,\n                            message.getThrowable());\n                    break;\n                case FileDownloadStatus.paused:\n                    if (largeFileListener != null) {\n                        largeFileListener.paused(originTask,\n                                message.getLargeSofarBytes(),\n                                message.getLargeTotalBytes());\n                    } else {\n                        listener.paused(originTask,\n                                message.getSmallSofarBytes(),\n                                message.getSmallTotalBytes());\n                    }\n                    break;\n                case FileDownloadStatus.warn:\n                    // already same url & path in pending/running list\n                    listener.warn(originTask);\n                    break;\n                default:\n                    // ignored\n            }\n        }\n    }\n\n    @Override\n    public boolean handoverDirectly() {\n        return mTask.getOrigin().isSyncCallback();\n    }\n\n    @Override\n    public void reAppointment(BaseDownloadTask.IRunningTask task,\n                              BaseDownloadTask.LifeCycleCallback callback) {\n        if (this.mTask != null) {\n            throw new IllegalStateException(\n                    FileDownloadUtils.formatString(\"the messenger is working, can't \"\n                            + \"re-appointment for %s\", task));\n        }\n\n        init(task, callback);\n    }\n\n    @Override\n    public boolean isBlockingCompleted() {\n        return parcelQueue.peek().getStatus() == FileDownloadStatus.blockComplete;\n    }\n\n    @Override\n    public void discard() {\n        mIsDiscard = true;\n    }\n\n    @Override\n    public String toString() {\n        return FileDownloadUtils.formatString(\"%d:%s\",\n                mTask == null ? -1 : mTask.getOrigin().getId(), super.toString());\n    }\n}\n"
  },
  {
    "path": "library/src/main/java/com/liulishuo/filedownloader/FileDownloadMonitor.java",
    "content": "/*\n * Copyright (c) 2015 LingoChamp Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *    http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.liulishuo.filedownloader;\n\n/**\n * The FileDownloader global monitor, monitor the begin、over for all tasks.\n *\n * @see BaseDownloadTask.LifeCycleCallback#onBegin()\n * @see BaseDownloadTask.LifeCycleCallback#onOver() ()\n * @see BaseDownloadTask#start()\n * @see FileDownloader#start(FileDownloadListener, boolean)\n */\npublic class FileDownloadMonitor {\n    private static IMonitor monitor;\n\n    public static void setGlobalMonitor(final IMonitor monitor) {\n        FileDownloadMonitor.monitor = monitor;\n    }\n\n    public static void releaseGlobalMonitor() {\n        monitor = null;\n    }\n\n    public static IMonitor getMonitor() {\n        return monitor;\n    }\n\n    public static boolean isValid() {\n        return getMonitor() != null;\n    }\n\n\n    /**\n     * The interface used to monitor all tasks's status change in the FileDownloader.\n     * <p/>\n     * All method in this interface will be invoked synchronous, recommend don't to hold the thread\n     * of invoking the method.\n     *\n     * @see FileDownloadMonitor#setGlobalMonitor(IMonitor)\n     */\n    public interface IMonitor {\n        /**\n         * Request to start multi-tasks manually.\n         *\n         * @param count  The count of tasks will start.\n         * @param serial Tasks will be started in serial or parallel.\n         * @param lis    The listener.\n         */\n        void onRequestStart(int count, boolean serial, FileDownloadListener lis);\n\n        /**\n         * Request to start a task.\n         *\n         * @param task The task will start.\n         */\n        void onRequestStart(BaseDownloadTask task);\n\n        /**\n         * The method will be invoked when the task in the internal is beginning.\n         *\n         * @param task The task is received to start internally.\n         */\n        void onTaskBegin(BaseDownloadTask task);\n\n        /**\n         * The method will be invoked when the download runnable of the task has started running.\n         *\n         * @param task The task finish pending and start download runnable.\n         */\n        void onTaskStarted(BaseDownloadTask task);\n\n        /**\n         * The method will be invoked when the task in the internal is over.\n         *\n         * @param task The task is over.\n         */\n        void onTaskOver(BaseDownloadTask task);\n    }\n\n}\n"
  },
  {
    "path": "library/src/main/java/com/liulishuo/filedownloader/FileDownloadQueueSet.java",
    "content": "/*\n * Copyright (c) 2015 LingoChamp Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *    http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.liulishuo.filedownloader;\n\nimport java.util.ArrayList;\nimport java.util.List;\n\n/**\n * The helper for start and config the task queue simply and quickly.\n *\n * @see FileDownloader#start(FileDownloadListener, boolean)\n */\n@SuppressWarnings({\"SameParameterValue\", \"WeakerAccess\"})\npublic class FileDownloadQueueSet {\n\n    private FileDownloadListener target;\n    private boolean isSerial;\n\n\n    private List<BaseDownloadTask.FinishListener> taskFinishListenerList;\n    private Integer autoRetryTimes;\n    private Boolean syncCallback;\n    private Boolean isForceReDownload;\n    private Boolean isWifiRequired;\n    private Integer callbackProgressTimes;\n    private Integer callbackProgressMinIntervalMillis;\n    private Object tag;\n    private String directory;\n\n    private BaseDownloadTask[] tasks;\n\n    /**\n     * @param target The download listener will be set to all tasks in this queue set.\n     */\n    public FileDownloadQueueSet(FileDownloadListener target) {\n        if (target == null) {\n            throw new IllegalArgumentException(\n                    \"create FileDownloadQueueSet must with valid target!\");\n        }\n        this.target = target;\n    }\n\n    /**\n     * Form a queue with same {@link #target} and will {@link #start()} in parallel.\n     */\n    public FileDownloadQueueSet downloadTogether(BaseDownloadTask... tasks) {\n        this.isSerial = false;\n        this.tasks = tasks;\n\n        return this;\n\n    }\n\n    /**\n     * Form a queue with same {@link #target} and will {@link #start()} in parallel.\n     */\n    public FileDownloadQueueSet downloadTogether(List<BaseDownloadTask> tasks) {\n        this.isSerial = false;\n        this.tasks = new BaseDownloadTask[tasks.size()];\n        tasks.toArray(this.tasks);\n\n        return this;\n\n    }\n\n    /**\n     * Form a queue with same {@link #target} and will {@link #start()} linearly.\n     */\n    public FileDownloadQueueSet downloadSequentially(BaseDownloadTask... tasks) {\n        this.isSerial = true;\n        this.tasks = tasks;\n\n        return this;\n    }\n\n    /**\n     * Form a queue with same {@link #target} and will {@link #start()} linearly.\n     */\n    public FileDownloadQueueSet downloadSequentially(List<BaseDownloadTask> tasks) {\n        this.isSerial = true;\n        this.tasks = new BaseDownloadTask[tasks.size()];\n        tasks.toArray(this.tasks);\n\n        return this;\n    }\n\n    /**\n     * Before starting downloading tasks in this queue-set, we will try to\n     * {@link BaseDownloadTask#reuse} tasks first.\n     */\n    public void reuseAndStart() {\n        for (BaseDownloadTask task : tasks) {\n            task.reuse();\n        }\n        start();\n    }\n\n    /**\n     * Start tasks in a queue.\n     *\n     * @see #downloadSequentially(BaseDownloadTask...)\n     * @see #downloadSequentially(List)\n     * @see #downloadTogether(BaseDownloadTask...)\n     * @see #downloadTogether(List)\n     */\n    public void start() {\n        for (BaseDownloadTask task : tasks) {\n            task.setListener(target);\n\n            if (autoRetryTimes != null) {\n                task.setAutoRetryTimes(autoRetryTimes);\n            }\n\n            if (syncCallback != null) {\n                task.setSyncCallback(syncCallback);\n            }\n\n            if (isForceReDownload != null) {\n                task.setForceReDownload(isForceReDownload);\n            }\n\n            if (callbackProgressTimes != null) {\n                task.setCallbackProgressTimes(callbackProgressTimes);\n            }\n\n            if (callbackProgressMinIntervalMillis != null) {\n                task.setCallbackProgressMinInterval(callbackProgressMinIntervalMillis);\n            }\n\n            if (tag != null) {\n                task.setTag(tag);\n            }\n\n            if (taskFinishListenerList != null) {\n                for (BaseDownloadTask.FinishListener finishListener : taskFinishListenerList) {\n                    task.addFinishListener(finishListener);\n                }\n            }\n\n            if (this.directory != null) {\n                task.setPath(this.directory, true);\n            }\n\n            if (this.isWifiRequired != null) {\n                task.setWifiRequired(this.isWifiRequired);\n            }\n\n            task.asInQueueTask().enqueue();\n        }\n\n        FileDownloader.getImpl().start(target, isSerial);\n    }\n\n    /**\n     * @param directory Set the {@code directory} to store files in this queue.\n     *                  All tasks in this queue will be invoked\n     *                  {@link BaseDownloadTask#setPath(String, boolean)} with params:\n     *                  ({@code directory}, {@code true}).\n     */\n    public FileDownloadQueueSet setDirectory(String directory) {\n        this.directory = directory;\n        return this;\n    }\n\n    /**\n     * @see BaseDownloadTask#setAutoRetryTimes(int)\n     */\n    public FileDownloadQueueSet setAutoRetryTimes(int autoRetryTimes) {\n        this.autoRetryTimes = autoRetryTimes;\n        return this;\n    }\n\n    /**\n     * @see BaseDownloadTask#setSyncCallback(boolean)\n     */\n    public FileDownloadQueueSet setSyncCallback(final boolean syncCallback) {\n        this.syncCallback = syncCallback;\n        return this;\n    }\n\n    /**\n     * @see BaseDownloadTask#setForceReDownload(boolean)\n     */\n    public FileDownloadQueueSet setForceReDownload(final boolean isForceReDownload) {\n        this.isForceReDownload = isForceReDownload;\n        return this;\n    }\n\n    /**\n     * @see BaseDownloadTask#setCallbackProgressTimes(int)\n     */\n    public FileDownloadQueueSet setCallbackProgressTimes(final int callbackProgressTimes) {\n        this.callbackProgressTimes = callbackProgressTimes;\n        return this;\n    }\n\n    /**\n     * @see BaseDownloadTask#setCallbackProgressMinInterval(int)\n     */\n    public FileDownloadQueueSet setCallbackProgressMinInterval(int minIntervalMillis) {\n        this.callbackProgressMinIntervalMillis = minIntervalMillis;\n        return this;\n    }\n\n    /**\n     * @see BaseDownloadTask#setCallbackProgressIgnored()\n     */\n    public FileDownloadQueueSet ignoreEachTaskInternalProgress() {\n        setCallbackProgressTimes(-1);\n        return this;\n    }\n\n    /**\n     * @see BaseDownloadTask#setCallbackProgressTimes(int)\n     */\n    public FileDownloadQueueSet disableCallbackProgressTimes() {\n        return setCallbackProgressTimes(0);\n    }\n\n    /**\n     * @see BaseDownloadTask#setTag(Object)\n     */\n    public FileDownloadQueueSet setTag(final Object tag) {\n        this.tag = tag;\n        return this;\n    }\n\n    /**\n     * @see BaseDownloadTask#addFinishListener(BaseDownloadTask.FinishListener)\n     */\n    public FileDownloadQueueSet addTaskFinishListener(\n            final BaseDownloadTask.FinishListener finishListener) {\n        if (this.taskFinishListenerList == null) {\n            this.taskFinishListenerList = new ArrayList<>();\n        }\n\n        this.taskFinishListenerList.add(finishListener);\n        return this;\n    }\n\n    /**\n     * @see BaseDownloadTask#setWifiRequired(boolean)\n     */\n    public FileDownloadQueueSet setWifiRequired(boolean isWifiRequired) {\n        this.isWifiRequired = isWifiRequired;\n        return this;\n    }\n\n}\n"
  },
  {
    "path": "library/src/main/java/com/liulishuo/filedownloader/FileDownloadSampleListener.java",
    "content": "/*\n * Copyright (c) 2015 LingoChamp Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *    http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.liulishuo.filedownloader;\n\n/**\n * Simplify the {@link FileDownloadListener}.\n */\npublic class FileDownloadSampleListener extends FileDownloadListener {\n\n    @Override\n    protected void pending(BaseDownloadTask task, int soFarBytes, int totalBytes) {\n\n    }\n\n    @Override\n    protected void progress(BaseDownloadTask task, int soFarBytes, int totalBytes) {\n\n    }\n\n    @Override\n    protected void blockComplete(BaseDownloadTask task) {\n\n    }\n\n    @Override\n    protected void completed(BaseDownloadTask task) {\n\n    }\n\n    @Override\n    protected void paused(BaseDownloadTask task, int soFarBytes, int totalBytes) {\n\n    }\n\n    @Override\n    protected void error(BaseDownloadTask task, Throwable e) {\n\n    }\n\n    @Override\n    protected void warn(BaseDownloadTask task) {\n\n    }\n}\n"
  },
  {
    "path": "library/src/main/java/com/liulishuo/filedownloader/FileDownloadServiceProxy.java",
    "content": "/*\n * Copyright (c) 2015 LingoChamp Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *    http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage com.liulishuo.filedownloader;\n\nimport android.app.Notification;\nimport android.content.Context;\n\nimport com.liulishuo.filedownloader.model.FileDownloadHeader;\nimport com.liulishuo.filedownloader.services.FDServiceSharedHandler;\nimport com.liulishuo.filedownloader.util.FileDownloadProperties;\n\n/**\n * The proxy used for executing the action from FileDownloader to FileDownloadService.\n *\n * @see FileDownloadServiceSharedTransmit In case of FileDownloadService runs in the main process.\n * @see FileDownloadServiceUIGuard In case of FileDownloadService runs in the separate\n * `:filedownloader` process.\n * <p/>\n * You can add a command `process.non-separate=true` to `/filedownloader.properties` to make the\n * FileDownloadService runs in the main process, and by default the FileDownloadService runs in the\n * separate `:filedownloader` process.\n */\npublic class FileDownloadServiceProxy implements IFileDownloadServiceProxy {\n\n    private static final class HolderClass {\n        private static final FileDownloadServiceProxy INSTANCE = new FileDownloadServiceProxy();\n    }\n\n    public static FileDownloadServiceProxy getImpl() {\n        return HolderClass.INSTANCE;\n    }\n\n    public static FDServiceSharedHandler.FileDownloadServiceSharedConnection\n    getConnectionListener() {\n        if (getImpl().handler instanceof FileDownloadServiceSharedTransmit) {\n            return (FDServiceSharedHandler.FileDownloadServiceSharedConnection) getImpl().handler;\n        }\n        return null;\n    }\n\n    private final IFileDownloadServiceProxy handler;\n\n    private FileDownloadServiceProxy() {\n        handler = FileDownloadProperties.getImpl().processNonSeparate\n                ? new FileDownloadServiceSharedTransmit()\n                : new FileDownloadServiceUIGuard();\n    }\n\n    @Override\n    public boolean start(String url, String path, boolean pathAsDirectory,\n                         int callbackProgressTimes,\n                         int callbackProgressMinIntervalMillis,\n                         int autoRetryTimes, boolean forceReDownload, FileDownloadHeader header,\n                         boolean isWifiRequired) {\n        return handler.start(url, path, pathAsDirectory, callbackProgressTimes,\n                callbackProgressMinIntervalMillis, autoRetryTimes, forceReDownload, header,\n                isWifiRequired);\n    }\n\n    @Override\n    public boolean pause(int id) {\n        return handler.pause(id);\n    }\n\n    @Override\n    public boolean isDownloading(String url, String path) {\n        return handler.isDownloading(url, path);\n    }\n\n    @Override\n    public long getSofar(int id) {\n        return handler.getSofar(id);\n    }\n\n    @Override\n    public long getTotal(int id) {\n        return handler.getTotal(id);\n    }\n\n    @Override\n    public byte getStatus(int id) {\n        return handler.getStatus(id);\n    }\n\n    @Override\n    public void pauseAllTasks() {\n        handler.pauseAllTasks();\n    }\n\n    @Override\n    public boolean isIdle() {\n        return handler.isIdle();\n    }\n\n    @Override\n    public boolean isConnected() {\n        return handler.isConnected();\n    }\n\n    @Override\n    public void bindStartByContext(Context context) {\n        handler.bindStartByContext(context);\n    }\n\n    @Override\n    public void bindStartByContext(Context context, Runnable connectedRunnable) {\n        handler.bindStartByContext(context, connectedRunnable);\n    }\n\n    @Override\n    public void unbindByContext(Context context) {\n        handler.unbindByContext(context);\n    }\n\n    @Override\n    public void startForeground(int notificationId, Notification notification) {\n        handler.startForeground(notificationId, notification);\n    }\n\n    @Override\n    public void stopForeground(boolean removeNotification) {\n        handler.stopForeground(removeNotification);\n    }\n\n    @Override\n    public boolean setMaxNetworkThreadCount(int count) {\n        return handler.setMaxNetworkThreadCount(count);\n    }\n\n    @Override\n    public boolean clearTaskData(int id) {\n        return handler.clearTaskData(id);\n    }\n\n    @Override\n    public void clearAllTaskData() {\n        handler.clearAllTaskData();\n    }\n\n    @Override\n    public boolean isRunServiceForeground() {\n        return handler.isRunServiceForeground();\n    }\n}\n"
  },
  {
    "path": "library/src/main/java/com/liulishuo/filedownloader/FileDownloadServiceSharedTransmit.java",
    "content": "/*\n * Copyright (c) 2015 LingoChamp Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *    http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage com.liulishuo.filedownloader;\n\nimport android.app.Notification;\nimport android.content.Context;\nimport android.content.Intent;\nimport android.os.Build;\n\nimport com.liulishuo.filedownloader.event.DownloadServiceConnectChangedEvent;\nimport com.liulishuo.filedownloader.model.FileDownloadHeader;\nimport com.liulishuo.filedownloader.services.FDServiceSharedHandler;\nimport com.liulishuo.filedownloader.services.FDServiceSharedHandler.FileDownloadServiceSharedConnection;\nimport com.liulishuo.filedownloader.services.FileDownloadService.SharedMainProcessService;\nimport com.liulishuo.filedownloader.util.DownloadServiceNotConnectedHelper;\nimport com.liulishuo.filedownloader.util.ExtraKeys;\nimport com.liulishuo.filedownloader.util.FileDownloadLog;\nimport com.liulishuo.filedownloader.util.FileDownloadUtils;\n\nimport java.util.ArrayList;\nimport java.util.List;\n\n/**\n * This transmit layer is used for the FileDownloader-Process is shared the main process.\n * <p/>\n * If you want use this transmit and want the FileDownloadService share the main process, not in the\n * separate process, just add a command `process.non-separate=true` in `/filedownloader.properties`.\n *\n * @see FileDownloadServiceUIGuard\n */\nclass FileDownloadServiceSharedTransmit implements\n        IFileDownloadServiceProxy, FileDownloadServiceSharedConnection {\n\n    private static final Class<?> SERVICE_CLASS = SharedMainProcessService.class;\n\n    private boolean runServiceForeground = false;\n\n    @Override\n    public boolean start(String url, String path, boolean pathAsDirectory,\n                         int callbackProgressTimes,\n                         int callbackProgressMinIntervalMillis,\n                         int autoRetryTimes, boolean forceReDownload, FileDownloadHeader header,\n                         boolean isWifiRequired) {\n        if (!isConnected()) {\n            return DownloadServiceNotConnectedHelper.start(url, path, pathAsDirectory);\n        }\n\n        handler.start(url, path, pathAsDirectory, callbackProgressTimes,\n                callbackProgressMinIntervalMillis,\n                autoRetryTimes, forceReDownload, header, isWifiRequired);\n        return true;\n    }\n\n    @Override\n    public boolean pause(int id) {\n        if (!isConnected()) {\n            return DownloadServiceNotConnectedHelper.pause(id);\n        }\n\n        return handler.pause(id);\n    }\n\n    @Override\n    public boolean isDownloading(String url, String path) {\n        if (!isConnected()) {\n            return DownloadServiceNotConnectedHelper.isDownloading(url, path);\n        }\n\n        return handler.checkDownloading(url, path);\n    }\n\n    @Override\n    public long getSofar(int id) {\n        if (!isConnected()) {\n            return DownloadServiceNotConnectedHelper.getSofar(id);\n        }\n\n        return handler.getSofar(id);\n    }\n\n    @Override\n    public long getTotal(int id) {\n        if (!isConnected()) {\n            return DownloadServiceNotConnectedHelper.getTotal(id);\n        }\n\n        return handler.getTotal(id);\n    }\n\n    @Override\n    public byte getStatus(int id) {\n        if (!isConnected()) {\n            return DownloadServiceNotConnectedHelper.getStatus(id);\n        }\n\n        return handler.getStatus(id);\n    }\n\n    @Override\n    public void pauseAllTasks() {\n        if (!isConnected()) {\n            DownloadServiceNotConnectedHelper.pauseAllTasks();\n            return;\n        }\n\n        handler.pauseAllTasks();\n    }\n\n    @Override\n    public boolean isIdle() {\n        if (!isConnected()) {\n            return DownloadServiceNotConnectedHelper.isIdle();\n        }\n\n        return handler.isIdle();\n    }\n\n    @Override\n    public boolean isConnected() {\n        return handler != null;\n    }\n\n    @Override\n    public void bindStartByContext(Context context) {\n        bindStartByContext(context, null);\n    }\n\n    private final ArrayList<Runnable> connectedRunnableList = new ArrayList<>();\n\n    @Override\n    public void bindStartByContext(Context context, Runnable connectedRunnable) {\n        if (connectedRunnable != null) {\n            if (!connectedRunnableList.contains(connectedRunnable)) {\n                connectedRunnableList.add(connectedRunnable);\n            }\n        }\n        Intent i = new Intent(context, SERVICE_CLASS);\n        runServiceForeground = FileDownloadUtils.needMakeServiceForeground(context);\n        i.putExtra(ExtraKeys.IS_FOREGROUND, runServiceForeground);\n        if (runServiceForeground) {\n            if (FileDownloadLog.NEED_LOG) FileDownloadLog.d(this, \"start foreground service\");\n            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) context.startForegroundService(i);\n        } else  {\n            context.startService(i);\n        }\n    }\n\n    @Override\n    public void unbindByContext(Context context) {\n        Intent i = new Intent(context, SERVICE_CLASS);\n        context.stopService(i);\n        handler = null;\n    }\n\n    @Override\n    public void startForeground(int notificationId, Notification notification) {\n        if (!isConnected()) {\n            DownloadServiceNotConnectedHelper.startForeground(notificationId, notification);\n            return;\n        }\n\n        handler.startForeground(notificationId, notification);\n    }\n\n    @Override\n    public void stopForeground(boolean removeNotification) {\n        if (!isConnected()) {\n            DownloadServiceNotConnectedHelper.stopForeground(removeNotification);\n            return;\n        }\n\n        handler.stopForeground(removeNotification);\n        runServiceForeground = false;\n    }\n\n    @Override\n    public boolean setMaxNetworkThreadCount(int count) {\n        if (!isConnected()) {\n            return DownloadServiceNotConnectedHelper.setMaxNetworkThreadCount(count);\n        }\n\n        return handler.setMaxNetworkThreadCount(count);\n    }\n\n    @Override\n    public boolean clearTaskData(int id) {\n        if (!isConnected()) {\n            return DownloadServiceNotConnectedHelper.clearTaskData(id);\n        }\n        return handler.clearTaskData(id);\n    }\n\n    @Override\n    public void clearAllTaskData() {\n        if (!isConnected()) {\n            DownloadServiceNotConnectedHelper.clearAllTaskData();\n            return;\n        }\n\n        handler.clearAllTaskData();\n    }\n\n    @Override\n    public boolean isRunServiceForeground() {\n        return runServiceForeground;\n    }\n\n    private FDServiceSharedHandler handler;\n\n    @Override\n    public void onConnected(final FDServiceSharedHandler handler) {\n        this.handler = handler;\n        @SuppressWarnings(\"unchecked\") final List<Runnable> runnableList =\n                (List<Runnable>) connectedRunnableList.clone();\n        connectedRunnableList.clear();\n        for (Runnable runnable : runnableList) {\n            runnable.run();\n        }\n\n        FileDownloadEventPool.getImpl().\n                asyncPublishInNewThread(new DownloadServiceConnectChangedEvent(\n                        DownloadServiceConnectChangedEvent.ConnectStatus.connected,\n                        SERVICE_CLASS));\n    }\n\n    @Override\n    public void onDisconnected() {\n        this.handler = null;\n        FileDownloadEventPool.getImpl().\n                asyncPublishInNewThread(new DownloadServiceConnectChangedEvent(\n                        DownloadServiceConnectChangedEvent.ConnectStatus.disconnected,\n                        SERVICE_CLASS));\n    }\n\n\n}\n"
  },
  {
    "path": "library/src/main/java/com/liulishuo/filedownloader/FileDownloadServiceUIGuard.java",
    "content": "/*\n * Copyright (c) 2015 LingoChamp Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *    http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.liulishuo.filedownloader;\n\nimport android.app.Notification;\nimport android.os.IBinder;\nimport android.os.RemoteException;\n\nimport com.liulishuo.filedownloader.i.IFileDownloadIPCCallback;\nimport com.liulishuo.filedownloader.i.IFileDownloadIPCService;\nimport com.liulishuo.filedownloader.message.MessageSnapshot;\nimport com.liulishuo.filedownloader.message.MessageSnapshotFlow;\nimport com.liulishuo.filedownloader.model.FileDownloadHeader;\nimport com.liulishuo.filedownloader.model.FileDownloadStatus;\nimport com.liulishuo.filedownloader.services.BaseFileServiceUIGuard;\nimport com.liulishuo.filedownloader.services.FileDownloadService.SeparateProcessService;\nimport com.liulishuo.filedownloader.util.DownloadServiceNotConnectedHelper;\n\n\n/**\n * The UI-Guard for FileDownloader-Process.\n * <p/>\n * The only Class can access the FileDownload-Process, and the only Class can receive the event from\n * the FileDownloader-Process through Binder.\n * <p/>\n * We will use this UIGuard as default, because the FileDownloadService runs in the separate process\n * `:filedownloader` as default, If you want to share the main process to run the\n * FileDownloadService, just add a command `process.non-separate=true` in\n * `/filedownloader.properties`.\n *\n * @see FileDownloadServiceSharedTransmit\n */\nclass FileDownloadServiceUIGuard extends\n        BaseFileServiceUIGuard<FileDownloadServiceUIGuard.FileDownloadServiceCallback,\n                IFileDownloadIPCService> {\n\n    FileDownloadServiceUIGuard() {\n        super(SeparateProcessService.class);\n    }\n\n    @Override\n    protected FileDownloadServiceCallback createCallback() {\n        return new FileDownloadServiceCallback();\n    }\n\n    @Override\n    protected IFileDownloadIPCService asInterface(IBinder service) {\n        return IFileDownloadIPCService.Stub.asInterface(service);\n    }\n\n    @Override\n    protected void registerCallback(IFileDownloadIPCService service,\n                                    FileDownloadServiceCallback fileDownloadServiceCallback)\n            throws RemoteException {\n        service.registerCallback(fileDownloadServiceCallback);\n    }\n\n    @Override\n    protected void unregisterCallback(IFileDownloadIPCService service,\n                                      FileDownloadServiceCallback fileDownloadServiceCallback)\n            throws RemoteException {\n        service.unregisterCallback(fileDownloadServiceCallback);\n    }\n\n    protected static class FileDownloadServiceCallback extends IFileDownloadIPCCallback.Stub {\n\n        @Override\n        public void callback(MessageSnapshot snapshot) throws RemoteException {\n            MessageSnapshotFlow.getImpl().inflow(snapshot);\n        }\n\n    }\n\n    /**\n     * @param url                   for download\n     * @param path                  for save download file\n     * @param callbackProgressTimes for callback progress times\n     * @param autoRetryTimes        for auto retry times when error\n     * @param header                for http header\n     */\n    @Override\n    public boolean start(final String url, final String path, final boolean pathAsDirectory,\n                         final int callbackProgressTimes,\n                         final int callbackProgressMinIntervalMillis,\n                         final int autoRetryTimes, final boolean forceReDownload,\n                         final FileDownloadHeader header, final boolean isWifiRequired) {\n        if (!isConnected()) {\n            return DownloadServiceNotConnectedHelper.start(url, path, pathAsDirectory);\n        }\n\n        try {\n            getService().start(url, path, pathAsDirectory, callbackProgressTimes,\n                    callbackProgressMinIntervalMillis, autoRetryTimes, forceReDownload, header,\n                    isWifiRequired);\n        } catch (RemoteException e) {\n            e.printStackTrace();\n            return false;\n        }\n\n        return true;\n    }\n\n    @Override\n    public boolean pause(final int id) {\n        if (!isConnected()) {\n            return DownloadServiceNotConnectedHelper.pause(id);\n        }\n\n        try {\n            return getService().pause(id);\n        } catch (RemoteException e) {\n            e.printStackTrace();\n        }\n\n        return false;\n    }\n\n    @Override\n    public boolean isDownloading(final String url, final String path) {\n        if (!isConnected()) {\n            return DownloadServiceNotConnectedHelper.isDownloading(url, path);\n        }\n\n        try {\n            return getService().checkDownloading(url, path);\n        } catch (RemoteException e) {\n            e.printStackTrace();\n        }\n\n        return false;\n    }\n\n    @Override\n    public long getSofar(final int id) {\n        if (!isConnected()) {\n            return DownloadServiceNotConnectedHelper.getSofar(id);\n        }\n\n        long val = 0;\n        try {\n            val = getService().getSofar(id);\n        } catch (RemoteException e) {\n            e.printStackTrace();\n        }\n\n        return val;\n    }\n\n    @Override\n    public long getTotal(final int id) {\n        if (!isConnected()) {\n            return DownloadServiceNotConnectedHelper.getTotal(id);\n        }\n\n        long val = 0;\n        try {\n            val = getService().getTotal(id);\n        } catch (RemoteException e) {\n            e.printStackTrace();\n        }\n\n        return val;\n    }\n\n    @Override\n    public byte getStatus(final int id) {\n        if (!isConnected()) {\n            return DownloadServiceNotConnectedHelper.getStatus(id);\n        }\n\n        byte status = FileDownloadStatus.INVALID_STATUS;\n        try {\n            status = getService().getStatus(id);\n        } catch (RemoteException e) {\n            e.printStackTrace();\n        }\n\n        return status;\n    }\n\n    @Override\n    public void pauseAllTasks() {\n        if (!isConnected()) {\n            DownloadServiceNotConnectedHelper.pauseAllTasks();\n            return;\n        }\n\n        try {\n            getService().pauseAllTasks();\n        } catch (RemoteException e) {\n            e.printStackTrace();\n        }\n    }\n\n    /**\n     * @return any error, will return true\n     */\n    @Override\n    public boolean isIdle() {\n        if (!isConnected()) {\n            return DownloadServiceNotConnectedHelper.isIdle();\n        }\n\n        try {\n            getService().isIdle();\n        } catch (RemoteException e) {\n            e.printStackTrace();\n        }\n\n        return true;\n    }\n\n    @Override\n    public void startForeground(int notificationId, Notification notification) {\n        if (!isConnected()) {\n            DownloadServiceNotConnectedHelper.startForeground(notificationId, notification);\n            return;\n        }\n\n        try {\n            getService().startForeground(notificationId, notification);\n        } catch (RemoteException e) {\n            e.printStackTrace();\n        }\n    }\n\n    @Override\n    public void stopForeground(boolean removeNotification) {\n        if (!isConnected()) {\n            DownloadServiceNotConnectedHelper.stopForeground(removeNotification);\n            return;\n        }\n\n        try {\n            getService().stopForeground(removeNotification);\n        } catch (RemoteException e) {\n            e.printStackTrace();\n        } finally {\n            runServiceForeground = false;\n        }\n    }\n\n    @Override\n    public boolean setMaxNetworkThreadCount(int count) {\n        if (!isConnected()) {\n            return DownloadServiceNotConnectedHelper.setMaxNetworkThreadCount(count);\n        }\n\n        try {\n            return getService().setMaxNetworkThreadCount(count);\n        } catch (RemoteException e) {\n            e.printStackTrace();\n        }\n\n        return false;\n    }\n\n    @Override\n    public boolean clearTaskData(int id) {\n        if (!isConnected()) {\n            return DownloadServiceNotConnectedHelper.clearTaskData(id);\n        }\n\n        try {\n            return getService().clearTaskData(id);\n        } catch (RemoteException e) {\n            e.printStackTrace();\n        }\n\n        return false;\n    }\n\n    @Override\n    public void clearAllTaskData() {\n        if (!isConnected()) {\n            DownloadServiceNotConnectedHelper.clearAllTaskData();\n            return;\n        }\n\n        try {\n            getService().clearAllTaskData();\n        } catch (RemoteException e) {\n            e.printStackTrace();\n        }\n    }\n}\n"
  },
  {
    "path": "library/src/main/java/com/liulishuo/filedownloader/FileDownloadTaskLauncher.java",
    "content": "/*\n * Copyright (c) 2015 LingoChamp Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *    http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.liulishuo.filedownloader;\n\nimport com.liulishuo.filedownloader.message.MessageSnapshotFlow;\nimport com.liulishuo.filedownloader.util.FileDownloadExecutors;\nimport com.liulishuo.filedownloader.util.FileDownloadLog;\n\nimport java.util.ArrayList;\nimport java.util.List;\nimport java.util.concurrent.LinkedBlockingQueue;\nimport java.util.concurrent.ThreadPoolExecutor;\n\n/**\n * The global launcher for launching tasks.\n */\nclass FileDownloadTaskLauncher {\n\n    private static class HolderClass {\n        private static final FileDownloadTaskLauncher INSTANCE = new FileDownloadTaskLauncher();\n\n        static {\n            // We add the message receiver to the message snapshot flow central, when there is a\n            // task request to launch.\n            MessageSnapshotFlow.getImpl().setReceiver(new MessageSnapshotGate());\n        }\n    }\n\n    public static FileDownloadTaskLauncher getImpl() {\n        return HolderClass.INSTANCE;\n    }\n\n    private final LaunchTaskPool mLaunchTaskPool = new LaunchTaskPool();\n\n    synchronized void launch(final ITaskHunter.IStarter taskStarter) {\n        mLaunchTaskPool.asyncExecute(taskStarter);\n    }\n\n    synchronized void expireAll() {\n        mLaunchTaskPool.expireAll();\n    }\n\n    synchronized void expire(final ITaskHunter.IStarter taskStarter) {\n        mLaunchTaskPool.expire(taskStarter);\n    }\n\n    synchronized void expire(final FileDownloadListener lis) {\n        mLaunchTaskPool.expire(lis);\n    }\n\n\n    private static class LaunchTaskPool {\n\n        private ThreadPoolExecutor mPool;\n\n        /**\n         * the queue to use for holding tasks before they are\n         * executed.  This queue will hold only the {@code Runnable}\n         * tasks submitted by the {@code execute} method.\n         */\n        private LinkedBlockingQueue<Runnable> mWorkQueue;\n\n        LaunchTaskPool() {\n            init();\n        }\n\n        public void asyncExecute(final ITaskHunter.IStarter taskStarter) {\n            mPool.execute(new LaunchTaskRunnable(taskStarter));\n        }\n\n        public void expire(ITaskHunter.IStarter starter) {\n            /**\n             * @see LaunchTaskRunnable#equals(Object)\n             */\n            //noinspection SuspiciousMethodCalls\n            mWorkQueue.remove(starter);\n        }\n\n        public void expire(final FileDownloadListener listener) {\n            if (listener == null) {\n                FileDownloadLog.w(this, \"want to expire by listener, but the listener provided is\"\n                        + \" null\");\n                return;\n            }\n\n            List<Runnable> needPauseList = new ArrayList<>();\n            for (Runnable runnable : mWorkQueue) {\n                final LaunchTaskRunnable launchTaskRunnable = (LaunchTaskRunnable) runnable;\n                if (launchTaskRunnable.isSameListener(listener)) {\n                    launchTaskRunnable.expire();\n                    needPauseList.add(runnable);\n                }\n            }\n\n            if (needPauseList.isEmpty()) {\n                return;\n            }\n\n            if (FileDownloadLog.NEED_LOG) {\n                FileDownloadLog.d(this, \"expire %d tasks with listener[%s]\", needPauseList.size(),\n                        listener);\n            }\n\n            for (Runnable runnable : needPauseList) {\n                mPool.remove(runnable);\n            }\n        }\n\n        public void expireAll() {\n            if (FileDownloadLog.NEED_LOG) {\n                FileDownloadLog.d(this, \"expire %d tasks\",\n                        mWorkQueue.size());\n            }\n\n            mPool.shutdownNow();\n            init();\n        }\n\n        private void init() {\n            mWorkQueue = new LinkedBlockingQueue<>();\n            mPool = FileDownloadExecutors.newDefaultThreadPool(3, mWorkQueue, \"LauncherTask\");\n        }\n\n    }\n\n    private static class LaunchTaskRunnable implements Runnable {\n        private final ITaskHunter.IStarter mTaskStarter;\n        private boolean mExpired;\n\n        LaunchTaskRunnable(final ITaskHunter.IStarter taskStarter) {\n            this.mTaskStarter = taskStarter;\n            this.mExpired = false;\n        }\n\n        @Override\n        public void run() {\n            if (mExpired) {\n                return;\n            }\n\n            mTaskStarter.start();\n        }\n\n        public boolean isSameListener(final FileDownloadListener listener) {\n            return mTaskStarter != null && mTaskStarter.equalListener(listener);\n        }\n\n        @SuppressWarnings(\"checkstyle:equalshashcode\")\n        @Override\n        public boolean equals(Object obj) {\n            return super.equals(obj) || obj == mTaskStarter;\n        }\n\n        public void expire() {\n            this.mExpired = true;\n        }\n    }\n}\n"
  },
  {
    "path": "library/src/main/java/com/liulishuo/filedownloader/FileDownloader.java",
    "content": "/*\n * Copyright (c) 2015 LingoChamp Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *    http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.liulishuo.filedownloader;\n\nimport android.app.Application;\nimport android.app.Notification;\nimport android.content.ComponentName;\nimport android.content.Context;\nimport android.content.ServiceConnection;\nimport android.os.IBinder;\n\nimport com.liulishuo.filedownloader.download.CustomComponentHolder;\nimport com.liulishuo.filedownloader.event.DownloadServiceConnectChangedEvent;\nimport com.liulishuo.filedownloader.model.FileDownloadStatus;\nimport com.liulishuo.filedownloader.model.FileDownloadTaskAtom;\nimport com.liulishuo.filedownloader.services.DownloadMgrInitialParams;\nimport com.liulishuo.filedownloader.util.FileDownloadHelper;\nimport com.liulishuo.filedownloader.util.FileDownloadLog;\nimport com.liulishuo.filedownloader.util.FileDownloadProperties;\nimport com.liulishuo.filedownloader.util.FileDownloadUtils;\n\nimport java.io.File;\nimport java.util.List;\n\n/**\n * The basic entrance for FileDownloader.\n *\n * @see com.liulishuo.filedownloader.services.FileDownloadService The service for FileDownloader.\n * @see FileDownloadProperties\n */\n@SuppressWarnings(\"WeakerAccess\")\npublic class FileDownloader {\n\n    /**\n     * You can invoke this method anytime before you using the FileDownloader.\n     * <p>\n     * If you want to register your own customize components please using\n     * {@link #setupOnApplicationOnCreate(Application)} on the {@link Application#onCreate()}\n     * instead.\n     *\n     * @param context the context of Application or Activity etc..\n     */\n    public static void setup(Context context) {\n        FileDownloadHelper.holdContext(context.getApplicationContext());\n    }\n\n    /**\n     * Using this method to setup the FileDownloader only you want to register your own customize\n     * components for Filedownloader, otherwise just using {@link #setup(Context)} instead.\n     * <p/>\n     * Please invoke this method on the {@link Application#onCreate()} because of the customize\n     * components must be assigned before FileDownloader is running.\n     * <p/>\n     * Such as:\n     * <p/>\n     * class MyApplication extends Application {\n     *     ...\n     *     public void onCreate() {\n     *          ...\n     *          FileDownloader.setupOnApplicationOnCreate(this)\n     *              .idGenerator(new MyIdGenerator())\n     *              .database(new MyDatabase())\n     *              ...\n     *              .commit();\n     *          ...\n     *     }\n     *     ...\n     * }\n     * @param application the application.\n     * @return the customize components maker.\n     */\n    public static DownloadMgrInitialParams.InitCustomMaker setupOnApplicationOnCreate(\n            Application application) {\n        final Context context = application.getApplicationContext();\n        FileDownloadHelper.holdContext(context);\n\n        DownloadMgrInitialParams.InitCustomMaker customMaker =\n                new DownloadMgrInitialParams.InitCustomMaker();\n        CustomComponentHolder.getImpl().setInitCustomMaker(customMaker);\n\n        return customMaker;\n    }\n\n    /**\n     * @deprecated please use {@link #setup(Context)} instead.\n     */\n    public static void init(final Context context) {\n        if (context == null) {\n            throw new IllegalArgumentException(\"the provided context must not be null!\");\n        }\n\n        setup(context);\n    }\n\n\n    /**\n     * @deprecated please using {@link #setupOnApplicationOnCreate(Application)} instead.\n     */\n    public static void init(final Context context,\n                            final DownloadMgrInitialParams.InitCustomMaker maker) {\n        if (FileDownloadLog.NEED_LOG) {\n            FileDownloadLog.d(FileDownloader.class, \"init Downloader with params: %s %s\",\n                    context, maker);\n        }\n\n        if (context == null) {\n            throw new IllegalArgumentException(\"the provided context must not be null!\");\n        }\n\n        FileDownloadHelper.holdContext(context.getApplicationContext());\n\n        CustomComponentHolder.getImpl().setInitCustomMaker(maker);\n    }\n\n    private static final class HolderClass {\n        private static final FileDownloader INSTANCE = new FileDownloader();\n    }\n\n    public static FileDownloader getImpl() {\n        return HolderClass.INSTANCE;\n    }\n\n    /**\n     * For avoiding missing screen frames.\n     * <p/>\n     * This mechanism is used for avoid methods in {@link FileDownloadListener} is invoked too\n     * frequent in result the system missing screen frames in the main thread.\n     * <p>\n     * We wrap the message package which size is {@link FileDownloadMessageStation#SUB_PACKAGE_SIZE}\n     * and post the package to the main thread with the interval:\n     * {@link FileDownloadMessageStation#INTERVAL} milliseconds.\n     * <p/>\n     * The default interval is 10ms, if {@code intervalMillisecond} equal to or less than 0, each\n     * callback in {@link FileDownloadListener} will be posted to the main thread immediately.\n     *\n     * @param intervalMillisecond The time interval between posting two message packages.\n     * @see #enableAvoidDropFrame()\n     * @see #disableAvoidDropFrame()\n     * @see #setGlobalHandleSubPackageSize(int)\n     */\n    public static void setGlobalPost2UIInterval(final int intervalMillisecond) {\n        FileDownloadMessageStation.INTERVAL = intervalMillisecond;\n    }\n\n    /**\n     * For avoiding missing screen frames.\n     * <p/>\n     * This mechanism is used for avoid methods in {@link FileDownloadListener} is invoked too\n     * frequent in result the system missing screen frames in the main thread.\n     * <p>\n     * We wrap the message package which size is {@link FileDownloadMessageStation#SUB_PACKAGE_SIZE}\n     * and post the package to the main thread with the interval:\n     * {@link FileDownloadMessageStation#INTERVAL} milliseconds.\n     * <p>\n     * The default count of message for a message package is 5.\n     *\n     * @param packageSize The count of message for a message package.\n     * @see #setGlobalPost2UIInterval(int)\n     */\n    public static void setGlobalHandleSubPackageSize(final int packageSize) {\n        if (packageSize <= 0) {\n            throw new IllegalArgumentException(\"sub package size must more than 0\");\n        }\n        FileDownloadMessageStation.SUB_PACKAGE_SIZE = packageSize;\n    }\n\n    /**\n     * Avoid missing screen frames, this leads to all callbacks in {@link FileDownloadListener} do\n     * not be invoked at once when it has already achieved to ensure callbacks don't be too frequent\n     *\n     * @see #isEnabledAvoidDropFrame()\n     * @see #setGlobalPost2UIInterval(int)\n     */\n    public static void enableAvoidDropFrame() {\n        setGlobalPost2UIInterval(FileDownloadMessageStation.DEFAULT_INTERVAL);\n    }\n\n    /**\n     * Disable avoiding missing screen frames, let all callbacks in {@link FileDownloadListener}\n     * can be invoked at once when it achieve.\n     *\n     * @see #isEnabledAvoidDropFrame()\n     * @see #setGlobalPost2UIInterval(int)\n     */\n    public static void disableAvoidDropFrame() {\n        setGlobalPost2UIInterval(-1);\n    }\n\n    /**\n     * @return {@code true} if enabled the function of avoiding missing screen frames.\n     * @see #enableAvoidDropFrame()\n     * @see #disableAvoidDropFrame()\n     * @see #setGlobalPost2UIInterval(int)\n     */\n    public static boolean isEnabledAvoidDropFrame() {\n        return FileDownloadMessageStation.isIntervalValid();\n    }\n\n    /**\n     * Create a download task.\n     */\n    public BaseDownloadTask create(final String url) {\n        return new DownloadTask(url);\n    }\n\n    /**\n     * Start the download queue by the same listener.\n     *\n     * @param listener Used to assemble tasks which is bound by the same {@code listener}\n     * @param isSerial Whether start tasks one by one rather than parallel.\n     * @return {@code true} if start tasks successfully.\n     */\n    public boolean start(final FileDownloadListener listener, final boolean isSerial) {\n\n        if (listener == null) {\n            FileDownloadLog.w(this, \"Tasks with the listener can't start, because the listener \"\n                    + \"provided is null: [null, %B]\", isSerial);\n            return false;\n        }\n\n\n        return isSerial\n                ? getQueuesHandler().startQueueSerial(listener)\n                : getQueuesHandler().startQueueParallel(listener);\n    }\n\n\n    /**\n     * Pause the download queue by the same {@code listener}.\n     *\n     * @param listener the listener.\n     * @see #pause(int)\n     */\n    public void pause(final FileDownloadListener listener) {\n        FileDownloadTaskLauncher.getImpl().expire(listener);\n        final List<BaseDownloadTask.IRunningTask> taskList =\n                FileDownloadList.getImpl().copy(listener);\n        for (BaseDownloadTask.IRunningTask task : taskList) {\n            task.getOrigin().pause();\n        }\n    }\n\n    /**\n     * Pause all tasks running in FileDownloader.\n     */\n    public void pauseAll() {\n        FileDownloadTaskLauncher.getImpl().expireAll();\n        final BaseDownloadTask.IRunningTask[] downloadList = FileDownloadList.getImpl().copy();\n        for (BaseDownloadTask.IRunningTask task : downloadList) {\n            task.getOrigin().pause();\n        }\n        // double check, for case: File Download progress alive but ui progress has died and relived\n        // so FileDownloadList not always contain all running task exactly.\n        if (FileDownloadServiceProxy.getImpl().isConnected()) {\n            FileDownloadServiceProxy.getImpl().pauseAllTasks();\n        } else {\n            PauseAllMarker.createMarker();\n        }\n    }\n\n    /**\n     * Pause downloading tasks with the {@code id}.\n     *\n     * @param id the {@code id} .\n     * @return The size of tasks has been paused.\n     * @see #pause(FileDownloadListener)\n     */\n    public int pause(final int id) {\n        List<BaseDownloadTask.IRunningTask> taskList = FileDownloadList.getImpl()\n                .getDownloadingList(id);\n        if (null == taskList || taskList.isEmpty()) {\n            FileDownloadLog.w(this, \"request pause but not exist %d\", id);\n            return 0;\n        }\n\n        for (BaseDownloadTask.IRunningTask task : taskList) {\n            task.getOrigin().pause();\n        }\n\n        return taskList.size();\n    }\n\n    /**\n     * Clear the data with the provided {@code id}.\n     * Normally used to deleting the data in filedownloader database, when it is paused or in\n     * downloading status. If you want to re-download it clearly.\n     * <p/>\n     * <strong>Note:</strong> YOU NO NEED to clear the data when it is already completed downloading\n     * because the data would be deleted when it completed downloading automatically by\n     * FileDownloader.\n     * <p>\n     * If there are tasks with the {@code id} in downloading, will be paused first;\n     * If delete the data with the {@code id} in the filedownloader database successfully, will try\n     * to delete its intermediate downloading file and downloaded file.\n     *\n     * @param id             the download {@code id}.\n     * @param targetFilePath the target path.\n     * @return {@code true} if the data with the {@code id} in filedownloader database was deleted,\n     * and tasks with the {@code id} was paused; {@code false} otherwise.\n     */\n    public boolean clear(final int id, final String targetFilePath) {\n        pause(id);\n\n        if (FileDownloadServiceProxy.getImpl().clearTaskData(id)) {\n            // delete the task data in the filedownloader database successfully or no data with the\n            // id in filedownloader database.\n            final File intermediateFile = new File(FileDownloadUtils.getTempPath(targetFilePath));\n            if (intermediateFile.exists()) {\n                //noinspection ResultOfMethodCallIgnored\n                intermediateFile.delete();\n            }\n\n            final File targetFile = new File(targetFilePath);\n            if (targetFile.exists()) {\n                //noinspection ResultOfMethodCallIgnored\n                targetFile.delete();\n            }\n\n            return true;\n        }\n\n        return false;\n    }\n\n    /**\n     * Clear all data in the filedownloader database.\n     * <p>\n     * <strong>Note:</strong> Normally, YOU NO NEED to clearAllTaskData manually, because the\n     * FileDownloader will maintain those data to ensure only if the data available for resuming\n     * can be kept automatically.\n     *\n     * @see #clear(int, String)\n     */\n    public void clearAllTaskData() {\n        pauseAll();\n\n        FileDownloadServiceProxy.getImpl().clearAllTaskData();\n    }\n\n    /**\n     * Get downloaded bytes so far by the downloadId.\n     */\n    public long getSoFar(final int downloadId) {\n        BaseDownloadTask.IRunningTask task = FileDownloadList.getImpl().get(downloadId);\n        if (task == null) {\n            return FileDownloadServiceProxy.getImpl().getSofar(downloadId);\n        }\n\n        return task.getOrigin().getLargeFileSoFarBytes();\n    }\n\n    /**\n     * Get the total bytes of the target file for the task with the {code id}.\n     */\n    public long getTotal(final int id) {\n        BaseDownloadTask.IRunningTask task = FileDownloadList.getImpl().get(id);\n        if (task == null) {\n            return FileDownloadServiceProxy.getImpl().getTotal(id);\n        }\n\n        return task.getOrigin().getLargeFileTotalBytes();\n    }\n\n    /**\n     * @param id The downloadId.\n     * @return The downloading status without cover the completed status (if completed you will\n     * receive\n     * {@link FileDownloadStatus#INVALID_STATUS} ).\n     * @see #getStatus(String, String)\n     * @see #getStatus(int, String)\n     */\n    public byte getStatusIgnoreCompleted(final int id) {\n        return getStatus(id, null);\n    }\n\n    /**\n     * @param url  The downloading URL.\n     * @param path The downloading file's path.\n     * @return The downloading status.\n     * @see #getStatus(int, String)\n     * @see #getStatusIgnoreCompleted(int)\n     */\n    public byte getStatus(final String url, final String path) {\n        return getStatus(FileDownloadUtils.generateId(url, path), path);\n    }\n\n    /**\n     * @param id   The downloadId.\n     * @param path The target file path.\n     * @return the downloading status.\n     * @see FileDownloadStatus\n     * @see #getStatus(String, String)\n     * @see #getStatusIgnoreCompleted(int)\n     */\n    public byte getStatus(final int id, final String path) {\n        byte status;\n        BaseDownloadTask.IRunningTask task = FileDownloadList.getImpl().get(id);\n        if (task == null) {\n            status = FileDownloadServiceProxy.getImpl().getStatus(id);\n        } else {\n            status = task.getOrigin().getStatus();\n        }\n\n        if (path != null && status == FileDownloadStatus.INVALID_STATUS) {\n            if (FileDownloadUtils.isFilenameConverted(FileDownloadHelper.getAppContext())\n                    && new File(path).exists()) {\n                status = FileDownloadStatus.completed;\n            }\n        }\n\n        return status;\n    }\n\n    /**\n     * Find the running task by {@code url} and default path, and replace its listener with\n     * the new one {@code listener}.\n     *\n     * @return The target task's DownloadId, if not exist target task, and replace failed, will be 0\n     * @see #replaceListener(int, FileDownloadListener)\n     * @see #replaceListener(String, String, FileDownloadListener)\n     */\n    public int replaceListener(String url, FileDownloadListener listener) {\n        return replaceListener(url, FileDownloadUtils.getDefaultSaveFilePath(url), listener);\n    }\n\n    /**\n     * Find the running task by {@code url} and {@code path}, and replace its listener with\n     * the new one {@code listener}.\n     *\n     * @return The target task's DownloadId, if not exist target task, and replace failed, will be 0\n     * @see #replaceListener(String, FileDownloadListener)\n     * @see #replaceListener(int, FileDownloadListener)\n     */\n    public int replaceListener(String url, String path, FileDownloadListener listener) {\n        return replaceListener(FileDownloadUtils.generateId(url, path), listener);\n    }\n\n    /**\n     * Find the running task by {@code id}, and replace its listener width the new one\n     * {@code listener}.\n     *\n     * @return The target task's DownloadId, if not exist target task, and replace failed, will be 0\n     * @see #replaceListener(String, FileDownloadListener)\n     * @see #replaceListener(String, String, FileDownloadListener)\n     */\n    public int replaceListener(int id, FileDownloadListener listener) {\n        final BaseDownloadTask.IRunningTask task = FileDownloadList.getImpl().get(id);\n        if (task == null) {\n            return 0;\n        }\n\n        task.getOrigin().setListener(listener);\n        return task.getOrigin().getId();\n    }\n\n    /**\n     * Start and bind the FileDownloader service.\n     * <p>\n     * <strong>Tips:</strong> The FileDownloader service will start and bind automatically when any\n     * task is request to start.\n     *\n     * @see #bindService(Runnable)\n     * @see #isServiceConnected()\n     * @see #addServiceConnectListener(FileDownloadConnectListener)\n     */\n    public void bindService() {\n        if (!isServiceConnected()) {\n            FileDownloadServiceProxy.getImpl()\n                    .bindStartByContext(FileDownloadHelper.getAppContext());\n        }\n    }\n\n    /**\n     * Start and bind the FileDownloader service and run {@code runnable} as soon as the binding is\n     * successful.\n     * <p>\n     * <strong>Tips:</strong> The FileDownloader service will start and bind automatically when any\n     * task is request to start.\n     *\n     * @param runnable the command will be executed as soon as the FileDownloader Service is\n     *                 successfully bound.\n     * @see #isServiceConnected()\n     * @see #bindService()\n     * @see #addServiceConnectListener(FileDownloadConnectListener)\n     */\n    public void bindService(final Runnable runnable) {\n        if (isServiceConnected()) {\n            runnable.run();\n        } else {\n            FileDownloadServiceProxy.getImpl().\n                    bindStartByContext(FileDownloadHelper.getAppContext(), runnable);\n        }\n    }\n\n    /**\n     * Unbind and stop the downloader service.\n     */\n    public void unBindService() {\n        if (isServiceConnected()) {\n            FileDownloadServiceProxy.getImpl().unbindByContext(FileDownloadHelper.getAppContext());\n        }\n    }\n\n    /**\n     * Unbind and stop the downloader service when there is no task running in the FileDownloader.\n     *\n     * @return {@code true} if unbind and stop the downloader service successfully, {@code false}\n     * there are some tasks running in the FileDownloader.\n     */\n    public boolean unBindServiceIfIdle() {\n        // check idle\n        if (!isServiceConnected()) {\n            return false;\n        }\n\n        if (FileDownloadList.getImpl().isEmpty()\n                && FileDownloadServiceProxy.getImpl().isIdle()) {\n            unBindService();\n            return true;\n        }\n\n        return false;\n    }\n\n    /**\n     * @return {@code true} if the downloader service has been started and connected.\n     */\n    public boolean isServiceConnected() {\n        return FileDownloadServiceProxy.getImpl().isConnected();\n    }\n\n    /**\n     * Add the listener for listening when the status of connection with the downloader service is\n     * changed.\n     *\n     * @param listener The downloader service connection listener.\n     * @see #removeServiceConnectListener(FileDownloadConnectListener)\n     */\n    public void addServiceConnectListener(final FileDownloadConnectListener listener) {\n        FileDownloadEventPool.getImpl().addListener(DownloadServiceConnectChangedEvent.ID,\n                listener);\n    }\n\n    /**\n     * Remove the listener for listening when the status of connection with the downloader service\n     * is changed.\n     *\n     * @param listener The downloader service connection listener.\n     * @see #addServiceConnectListener(FileDownloadConnectListener)\n     */\n    public void removeServiceConnectListener(final FileDownloadConnectListener listener) {\n        FileDownloadEventPool.getImpl().removeListener(DownloadServiceConnectChangedEvent.ID,\n                listener);\n    }\n\n    /**\n     * Start the {@code notification} with the {@code id}. This will let the downloader service\n     * change to a foreground service.\n     * <p>\n     * In foreground status, will save the FileDownloader alive, even user kill the application\n     * from recent apps.\n     * <p/>\n     * Make FileDownloader service run in the foreground, supplying the ongoing\n     * notification to be shown to the user while in this state.\n     * By default FileDownloader services are background, meaning that if the system needs to\n     * kill them to reclaim more memory (such as to display a large page in a\n     * web browser), they can be killed without too much harm.  You can set this\n     * flag if killing your service would be disruptive to the user, such as\n     * if your service is performing background downloading, so the user\n     * would notice if their app stopped downloading.\n     *\n     * @param id           The identifier for this notification as per\n     *                     {@link android.app.NotificationManager#notify(int, Notification)\n     *                     NotificationManager.notify(int, Notification)}; must not be 0.\n     * @param notification The notification to be displayed.\n     * @see #stopForeground(boolean)\n     */\n    public void startForeground(int id, Notification notification) {\n        FileDownloadServiceProxy.getImpl().startForeground(id, notification);\n    }\n\n    /**\n     * Remove the downloader service from the foreground state, allowing it to be killed if\n     * more memory is needed.\n     *\n     * @param removeNotification {@code true} if the notification previously provided\n     *                           to {@link #startForeground} will be removed. {@code false} it will\n     *                           be remained until a later call removes it (or the service is\n     *                           destroyed).\n     * @see #startForeground(int, Notification)\n     */\n    public void stopForeground(boolean removeNotification) {\n        FileDownloadServiceProxy.getImpl().stopForeground(removeNotification);\n    }\n\n    /**\n     * @param url        The url of the completed task.\n     * @param path       The absolute path of the completed task's save file.\n     * @param totalBytes The content-length of the completed task, the length of the file in the\n     *                   {@code path} must be equal to this value.\n     * @return Whether is successful to set the task completed. If the {@code path} not exist will\n     * be false; If the length of the file in {@code path} is not equal to {@code totalBytes} will\n     * be false; If the task with {@code url} and {@code path} is downloading will be false.\n     * Otherwise will be true.\n     * @see FileDownloadUtils#isFilenameConverted(Context)\n     * <p>\n     * <p/>\n     * Recommend used to telling the FileDownloader Engine that the task with the {@code url}  and\n     * the {@code path} has already completed downloading, in case of your task has already\n     * downloaded by other ways(not by FileDownloader Engine), and after success to set the task\n     * completed, FileDownloader will check the task with {@code url} and the {@code path} whether\n     * completed by {@code totalBytes}.\n     * <p/>\n     * Otherwise, If FileDownloader Engine isn't know your task's status, whatever your task with\n     * the {@code url} and the {@code path} has already downloaded in other way, FileDownloader\n     * Engine will ignore the exist file and redownload it, because FileDownloader Engine don't know\n     * the exist file whether it is valid.\n     * @see #setTaskCompleted(List)\n     * @deprecated If you invoked this method, please remove the code directly feel free, it doesn't\n     * need any longer. In new mechanism(filedownloader 0.3.3 or higher), FileDownloader doesn't\n     * store completed tasks in Database anymore, because all downloading files have temp a file\n     * name.\n     */\n    @SuppressWarnings(\"UnusedParameters\")\n    public boolean setTaskCompleted(String url, String path, long totalBytes) {\n        FileDownloadLog.w(this, \"If you invoked this method, please remove it directly feel free, \"\n                + \"it doesn't need any longer\");\n        return true;\n    }\n\n    /**\n     * Recommend used to telling the FileDownloader Engine that a bulk of tasks have already\n     * downloaded by other ways(not by the FileDownloader Engine).\n     * <p/>\n     * The FileDownloader Engine need to know the status of completed, because if you want to\n     * download any tasks, FileDownloader Engine judges whether the task need downloads or not\n     * according its status which existed in DB.\n     *\n     * @param taskAtomList The bulk of tasks.\n     * @return Whether is successful to update all tasks' status to the Filedownloader Engine. If\n     * one task atom among them is not match the Rules in\n     * FileDownloadMgr#obtainCompletedTaskShelfModel(String, String, long)\n     * will receive false, and non of them would be updated to DB.\n     * @see #setTaskCompleted(String, String, long)\n     * @deprecated If you invoked this method, please remove the code directly feel free, it doesn't\n     * need any longer. In new mechanism(filedownloader 0.3.3 or higher), FileDownloader doesn't\n     * store completed tasks in Database anymore, because all downloading files have temp a file\n     * name.\n     */\n    @SuppressWarnings(\"UnusedParameters\")\n    public boolean setTaskCompleted(\n            @SuppressWarnings(\"deprecation\") List<FileDownloadTaskAtom> taskAtomList) {\n        FileDownloadLog.w(this, \"If you invoked this method, please remove it directly feel free, \"\n                + \"it doesn't need any longer\");\n        return true;\n    }\n\n    /**\n     * Set the maximum count of the network thread, what is the number of simultaneous downloads in\n     * FileDownloader.\n     *\n     * @param count the number of simultaneous downloads, scope: [1, 12].\n     * @return whether is successful to set the max network thread count.\n     * If there are any actively executing tasks in FileDownloader, you will receive a warn\n     * priority log int the logcat and this operation would be failed.\n     */\n    public boolean setMaxNetworkThreadCount(final int count) {\n        if (!FileDownloadList.getImpl().isEmpty()) {\n            FileDownloadLog.w(this, \"Can't change the max network thread count, because there \"\n                    + \"are actively executing tasks in FileDownloader, please try again after all\"\n                    + \" actively executing tasks are completed or invoking FileDownloader#pauseAll\"\n                    + \" directly.\");\n            return false;\n        }\n\n        return FileDownloadServiceProxy.getImpl().setMaxNetworkThreadCount(count);\n    }\n\n    /**\n     * If the FileDownloader service is not started and connected, FileDownloader will try to start\n     * it and try to bind with it. The current thread will also be blocked until the FileDownloader\n     * service is started and a connection is established, and then the request you\n     * invoke in {@link FileDownloadLine} will be executed.\n     * <p>\n     * If the FileDownloader service has been started and connected, the request you invoke in\n     * {@link FileDownloadLine} will be executed immediately.\n     * <p>\n     * <strong>Note:</strong> FileDownloader can not block the main thread, because the system is\n     * also call-backs the {@link ServiceConnection#onServiceConnected(ComponentName, IBinder)}\n     * method in the main thread.\n     * <p>\n     * <strong>Tips:</strong> The FileDownloader service will start and bind automatically when any\n     * task is request to start.\n     *\n     * @see FileDownloadLine\n     * @see #bindService(Runnable)\n     */\n    public FileDownloadLine insureServiceBind() {\n        return new FileDownloadLine();\n    }\n\n    /**\n     * If the FileDownloader service is not started and connected will return {@code false}\n     * immediately, and meanwhile FileDownloader will try to start FileDownloader service and try to\n     * bind with it, and after it is bound successfully the request you invoke in\n     * {@link FileDownloadLineAsync} will be executed automatically.\n     * <p>\n     * If the FileDownloader service has been started and connected, the request you invoke in\n     * {@link FileDownloadLineAsync} will be executed immediately.\n     *\n     * @see FileDownloadLineAsync\n     * @see #bindService(Runnable)\n     */\n    public FileDownloadLineAsync insureServiceBindAsync() {\n        return new FileDownloadLineAsync();\n    }\n\n    private static final Object INIT_QUEUES_HANDLER_LOCK = new Object();\n    private IQueuesHandler mQueuesHandler;\n\n    IQueuesHandler getQueuesHandler() {\n        if (mQueuesHandler == null) {\n            synchronized (INIT_QUEUES_HANDLER_LOCK) {\n                if (mQueuesHandler == null) {\n                    mQueuesHandler = new QueuesHandler();\n                }\n            }\n        }\n        return mQueuesHandler;\n    }\n\n    private static final Object INIT_LOST_CONNECTED_HANDLER_LOCK = new Object();\n    private ILostServiceConnectedHandler mLostConnectedHandler;\n\n    ILostServiceConnectedHandler getLostConnectedHandler() {\n        if (mLostConnectedHandler == null) {\n            synchronized (INIT_LOST_CONNECTED_HANDLER_LOCK) {\n                if (mLostConnectedHandler == null) {\n                    mLostConnectedHandler = new LostServiceConnectedHandler();\n                    addServiceConnectListener((FileDownloadConnectListener) mLostConnectedHandler);\n                }\n            }\n        }\n\n        return mLostConnectedHandler;\n    }\n\n\n}\n"
  },
  {
    "path": "library/src/main/java/com/liulishuo/filedownloader/IDownloadSpeed.java",
    "content": "/*\n * Copyright (c) 2015 LingoChamp Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *    http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.liulishuo.filedownloader;\n\n/**\n * The interface for the downloading speed.\n */\n\npublic interface IDownloadSpeed {\n\n    /**\n     * The downloading monitor, used for calculating downloading speed.\n     */\n    interface Monitor {\n        /**\n         * Start the monitor.\n         */\n        void start(long startBytes);\n\n        /**\n         * End the monitor, and calculate the average speed during the entire downloading processing\n         *\n         * @param sofarBytes The so far downloaded bytes.\n         */\n        void end(final long sofarBytes);\n\n        /**\n         * Refresh the downloading speed.\n         *\n         * @param sofarBytes The so far downloaded bytes.\n         */\n        void update(long sofarBytes);\n\n        /**\n         * Reset the monitor.\n         */\n        void reset();\n\n    }\n\n    /**\n     * For lookup the downloading speed data.\n     */\n    interface Lookup {\n        /**\n         * @return The currently downloading speed when the task is running.\n         * The average speed when the task is finished.\n         */\n        int getSpeed();\n\n        /**\n         * @param minIntervalUpdateSpeed The minimum interval to update the speed, used to adjust\n         *                               the refresh frequent.\n         */\n        void setMinIntervalUpdateSpeed(int minIntervalUpdateSpeed);\n    }\n}\n"
  },
  {
    "path": "library/src/main/java/com/liulishuo/filedownloader/IFileDownloadMessenger.java",
    "content": "/*\n * Copyright (c) 2015 LingoChamp Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *    http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.liulishuo.filedownloader;\n\nimport com.liulishuo.filedownloader.download.DownloadStatusCallback;\nimport com.liulishuo.filedownloader.message.MessageSnapshot;\nimport com.liulishuo.filedownloader.util.FileDownloadHelper;\n\n/**\n * @see com.liulishuo.filedownloader.model.FileDownloadStatus\n */\ninterface IFileDownloadMessenger {\n\n    /**\n     * The task is just received to handle.\n     * <p/>\n     * FileDownloader accept the task.\n     *\n     * @return Whether allow it to begin.\n     */\n    boolean notifyBegin();\n\n    /**\n     * The task is pending.\n     * <p/>\n     * enqueue, and pending, waiting.\n     *\n     * @see com.liulishuo.filedownloader.services.FileDownloadThreadPool\n     */\n    void notifyPending(MessageSnapshot snapshot);\n\n    /**\n     * The download runnable of the task has started running.\n     * <p/>\n     * Finish pending, and start download runnable.\n     *\n     * @see DownloadStatusCallback#onStartThread()\n     */\n    void notifyStarted(MessageSnapshot snapshot);\n\n    /**\n     * The task is running.\n     * <p/>\n     * Already connected to the server, and received the Http-response.\n     *\n     * @see DownloadStatusCallback#onConnected(boolean, long, String, String)\n     */\n    void notifyConnected(MessageSnapshot snapshot);\n\n    /**\n     * The task is running.\n     * <p/>\n     * Fetching datum, and write to local disk.\n     *\n     * @see DownloadStatusCallback#onProgress(long)\n     */\n    void notifyProgress(MessageSnapshot snapshot);\n\n    /**\n     * The task is running.\n     * <p/>\n     * Already completed download, and block the current thread to do something, such as unzip,etc.\n     *\n     * @see DownloadStatusCallback#onCompletedDirectly()\n     */\n    void notifyBlockComplete(MessageSnapshot snapshot);\n\n    /**\n     * The task over.\n     * <p/>\n     * Occur a exception when downloading, but has retry\n     * chance {@link BaseDownloadTask#setAutoRetryTimes(int)}, so retry(re-connect,re-download).\n     */\n    void notifyRetry(MessageSnapshot snapshot);\n\n    /**\n     * The task over.\n     * <p/>\n     * There has already had some same Tasks(Same-URL & Same-SavePath) in Pending-Queue or is\n     * running.\n     *\n     * @see FileDownloadHelper#inspectAndInflowDownloading\n     */\n    void notifyWarn(MessageSnapshot snapshot);\n\n    /**\n     * The task is over.\n     * <p/>\n     * Occur a exception, but don't has any chance to retry.\n     *\n     * @see DownloadStatusCallback#onErrorDirectly(Exception)\n     * @see com.liulishuo.filedownloader.exception.FileDownloadHttpException\n     * @see com.liulishuo.filedownloader.exception.FileDownloadOutOfSpaceException\n     * @see com.liulishuo.filedownloader.exception.FileDownloadGiveUpRetryException\n     */\n    void notifyError(MessageSnapshot snapshot);\n\n    /**\n     * The task is over.\n     * <p/>\n     * Pause manually by {@link BaseDownloadTask#pause()}.\n     *\n     * @see BaseDownloadTask#pause()\n     */\n    void notifyPaused(MessageSnapshot snapshot);\n\n    /**\n     * The task is over.\n     * <p/>\n     * Achieve complete ceremony.\n     *\n     * @see DownloadStatusCallback#onCompletedDirectly()\n     */\n    void notifyCompleted(MessageSnapshot snapshot);\n\n    /**\n     * Handover a message to {@link FileDownloadListener}.\n     */\n    void handoverMessage();\n\n    /**\n     * @return {@code true} if handover a message to {@link FileDownloadListener} directly(do not\n     * need to post the callback to the main thread).\n     * @see BaseDownloadTask#isSyncCallback()\n     */\n    boolean handoverDirectly();\n\n    /**\n     * @param task Re-appointment for this task, when this messenger has already accomplished the\n     *             old one.\n     */\n    void reAppointment(BaseDownloadTask.IRunningTask task,\n                       BaseDownloadTask.LifeCycleCallback callback);\n\n    /**\n     * The 'block completed'(status) message will be handover in the non-UI thread and block the\n     * 'completed'(status) message.\n     *\n     * @return {@code true} if the status of the current message is\n     * {@link com.liulishuo.filedownloader.model.FileDownloadStatus#blockComplete}.\n     */\n    boolean isBlockingCompleted();\n\n    /**\n     * Discard this messenger.\n     * <p>\n     * If this messenger is discarded, all messages sent by this messenger or feature messages\n     * handled by this messenger will be discard, no longer callback to the target Listener.\n     */\n    void discard();\n}\n"
  },
  {
    "path": "library/src/main/java/com/liulishuo/filedownloader/IFileDownloadServiceProxy.java",
    "content": "/*\n * Copyright (c) 2015 LingoChamp Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *    http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage com.liulishuo.filedownloader;\n\nimport android.app.Notification;\nimport android.content.Context;\n\nimport com.liulishuo.filedownloader.model.FileDownloadHeader;\n\n/**\n * The interface to access the FileDownloadService.\n */\npublic interface IFileDownloadServiceProxy {\n    boolean start(final String url, final String path, final boolean pathAsDirectory,\n                  final int callbackProgressTimes,\n                  final int callbackProgressMinIntervalMillis,\n                  final int autoRetryTimes, boolean forceReDownload,\n                  final FileDownloadHeader header, boolean isWifiRequired);\n\n    boolean pause(final int id);\n\n    boolean isDownloading(final String url, final String path);\n\n    long getSofar(final int downloadId);\n\n    long getTotal(final int downloadId);\n\n    byte getStatus(final int downloadId);\n\n    void pauseAllTasks();\n\n    boolean isIdle();\n\n    boolean isConnected();\n\n    void bindStartByContext(final Context context);\n\n    void bindStartByContext(final Context context, final Runnable connectedRunnable);\n\n    void unbindByContext(final Context context);\n\n    void startForeground(int id, Notification notification);\n\n    void stopForeground(boolean removeNotification);\n\n    boolean setMaxNetworkThreadCount(int count);\n\n    boolean clearTaskData(int id);\n\n    void clearAllTaskData();\n\n    boolean isRunServiceForeground();\n}\n"
  },
  {
    "path": "library/src/main/java/com/liulishuo/filedownloader/ILostServiceConnectedHandler.java",
    "content": "/*\n * Copyright (c) 2015 LingoChamp Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *    http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.liulishuo.filedownloader;\n\n/**\n * The handler for handing the case of the connect with the downloader service is lost when tasks is\n * running.\n */\n\npublic interface ILostServiceConnectedHandler {\n\n    /**\n     * @return Whether in the waiting list, what is waiting for the downloader service reconnected,\n     * and when it reconnected, all tasks in the waiting list will be started.\n     */\n    boolean isInWaitingList(BaseDownloadTask.IRunningTask task);\n\n    /**\n     * @param task is works well, so it will be removed from the waiting list.\n     */\n    void taskWorkFine(BaseDownloadTask.IRunningTask task);\n\n    /**\n     * @return {@code true} if the start action was dispatched.\n     */\n    boolean dispatchTaskStart(BaseDownloadTask.IRunningTask task);\n}\n"
  },
  {
    "path": "library/src/main/java/com/liulishuo/filedownloader/IQueuesHandler.java",
    "content": "/*\n * Copyright (c) 2015 LingoChamp Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *    http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.liulishuo.filedownloader;\n\nimport java.util.List;\n\n/**\n * The interface for handle affairs of queues.\n */\n\npublic interface IQueuesHandler {\n\n    /**\n     * Start tasks which the same {@code listener} as a queue, and execute theme in parallel.\n     *\n     * @param listener Used to assemble tasks which is bound by the same {@code listener}\n     * @return Whether start tasks successfully.\n     */\n    boolean startQueueParallel(FileDownloadListener listener);\n\n    /**\n     * Start tasks which the same {@code listener} as a queue, and execute theme one by one.\n     *\n     * @param listener Used to assemble tasks which is bound by the same {@code listener}\n     * @return Whether start tasks successfully.\n     */\n    boolean startQueueSerial(FileDownloadListener listener);\n\n    void freezeAllSerialQueues();\n\n    void unFreezeSerialQueues(List<Integer> attachKeyList);\n\n    int serialQueueSize();\n\n    boolean contain(int attachKey);\n}\n"
  },
  {
    "path": "library/src/main/java/com/liulishuo/filedownloader/ITaskHunter.java",
    "content": "/*\n * Copyright (c) 2015 LingoChamp Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *    http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.liulishuo.filedownloader;\n\nimport com.liulishuo.filedownloader.message.MessageSnapshot;\n\n/**\n * The downloading task hunter.\n */\n\npublic interface ITaskHunter extends IDownloadSpeed.Lookup {\n    /**\n     * Enter into the launch pool.\n     *\n     * @see FileDownloadTaskLauncher\n     */\n    void intoLaunchPool();\n\n    /**\n     * @return Whether pause the task successfully.\n     */\n    boolean pause();\n\n    /**\n     * @return the status.\n     * @see com.liulishuo.filedownloader.model.FileDownloadStatus\n     */\n    byte getStatus();\n\n    /**\n     * Reset the hunter.\n     */\n    void reset();\n\n    /**\n     * @return The so far downloaded bytes.\n     */\n    long getSofarBytes();\n\n    /**\n     * @return The total bytes.\n     */\n    long getTotalBytes();\n\n    /**\n     * @return {@code Null} if has didn't occurred any error yet.\n     */\n    Throwable getErrorCause();\n\n    /**\n     * @return The currently retrying times.\n     */\n    int getRetryingTimes();\n\n    /**\n     * @return {@code true} if didn't real start downloading but the old-file with target-path is\n     * exist, and just reuse it. {@code false} otherwise.\n     */\n    boolean isReusedOldFile();\n\n    /**\n     * @return {@code true} if the currently downloading is the downloading resuming from the\n     * breakpoint. {@code false} downloading from the beginning.\n     */\n    boolean isResuming();\n\n    /**\n     * @return The Etag from the response's header.\n     */\n    String getEtag();\n\n    /**\n     * @return {@code true} if the file length is large than 1.99G, {@code false} otherwise.\n     */\n    boolean isLargeFile();\n\n    /**\n     * Free the current hunter.\n     */\n    void free();\n\n    /**\n     * @see FileDownloadTaskLauncher\n     * <p>\n     * The starter for the downloading task.\n     */\n    interface IStarter {\n        /**\n         * Start the task in the launcher thread.\n         */\n        void start();\n\n        /**\n         * @param listener The downloading listener.\n         * @return {@code true} if {@code listener} equal to the listener of the current task.\n         */\n        boolean equalListener(FileDownloadListener listener);\n    }\n\n    /**\n     * The message handler for a task.\n     */\n    interface IMessageHandler {\n\n        /**\n         * Try to dispatch the {@code snapshot} with the keep ahead policy.\n         *\n         * @param snapshot the received message snapshot.\n         * @return {@code true} the message has been dispatched successfully.\n         */\n        boolean updateKeepAhead(final MessageSnapshot snapshot);\n\n        /**\n         * Try to dispatch the {@code snapshot} with the keep right flow policy.\n         *\n         * @param snapshot the received message snapshot.\n         * @return {@code true} the message has been dispatched successfully.\n         */\n        boolean updateKeepFlow(final MessageSnapshot snapshot);\n\n        /**\n         * Try to dispatch the {@code snapshot} with the more likely completed policy.\n         * <p>\n         * The more likely completed policy: in some case the snapshot more likely to a waiting to\n         * complete task.\n         *\n         * @param snapshot the received message snapshot.\n         * @return {@code true} the message has been dispatched successfully.\n         */\n        boolean updateMoreLikelyCompleted(final MessageSnapshot snapshot);\n\n        /**\n         * Try to dispatch the {@code snapshot} with the same file path policy.\n         * <p>\n         * The same file path policy: when the path provided by the user is a directory, we find the\n         * filename until the response from the service has been received, in this case when we find\n         * the target file path maybe can equal to another running task, so in this case, this task\n         * need callback a warn message.\n         *\n         * @param snapshot the received message snapshot.\n         * @return {@code true} the message has been dispatched successfully.\n         */\n        boolean updateSameFilePathTaskRunning(final MessageSnapshot snapshot);\n\n        /**\n         * @return The messenger for the message handler.\n         */\n        IFileDownloadMessenger getMessenger();\n\n        /**\n         * @param cause The cause of occurred exception.\n         * @return This message snapshot capture from the {@code cause}.\n         */\n        MessageSnapshot prepareErrorMessage(Throwable cause);\n    }\n}\n"
  },
  {
    "path": "library/src/main/java/com/liulishuo/filedownloader/IThreadPoolMonitor.java",
    "content": "/*\n * Copyright (c) 2015 LingoChamp Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *    http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.liulishuo.filedownloader;\n\nimport com.liulishuo.filedownloader.model.FileDownloadModel;\n\n/**\n * The FileDownload thread monitor interface.\n */\n\npublic interface IThreadPoolMonitor {\n    boolean isDownloading(FileDownloadModel model);\n\n    int findRunningTaskIdBySameTempPath(String tempFilePath, int excludeId);\n}\n"
  },
  {
    "path": "library/src/main/java/com/liulishuo/filedownloader/LostServiceConnectedHandler.java",
    "content": "/*\n * Copyright (c) 2015 LingoChamp Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *    http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.liulishuo.filedownloader;\n\nimport com.liulishuo.filedownloader.event.DownloadServiceConnectChangedEvent;\nimport com.liulishuo.filedownloader.util.FileDownloadHelper;\nimport com.liulishuo.filedownloader.util.FileDownloadLog;\n\nimport java.util.ArrayList;\nimport java.util.List;\n\n/**\n * The handler for handing the case of the connect with the downloader service is lost when tasks is\n * running.\n */\n\npublic class LostServiceConnectedHandler extends FileDownloadConnectListener implements\n        ILostServiceConnectedHandler {\n\n    private final ArrayList<BaseDownloadTask.IRunningTask> mWaitingList = new ArrayList<>();\n\n    @Override\n    public void connected() {\n        final IQueuesHandler queueHandler = FileDownloader.getImpl().getQueuesHandler();\n        final List<BaseDownloadTask.IRunningTask> copyWaitingList;\n\n        if (FileDownloadLog.NEED_LOG) {\n            FileDownloadLog.d(this, \"The downloader service is connected.\");\n        }\n\n        synchronized (mWaitingList) {\n            //noinspection unchecked\n            copyWaitingList = (List<BaseDownloadTask.IRunningTask>) mWaitingList.clone();\n            mWaitingList.clear();\n\n            final List<Integer> wakeupSerialQueueKeyList =\n                    new ArrayList<>(queueHandler.serialQueueSize());\n\n            for (BaseDownloadTask.IRunningTask task : copyWaitingList) {\n                final int attachKey = task.getAttachKey();\n                if (queueHandler.contain(attachKey)) {\n                    task.getOrigin().asInQueueTask().enqueue();\n\n                    if (!wakeupSerialQueueKeyList.contains(attachKey)) {\n                        wakeupSerialQueueKeyList.add(attachKey);\n                    }\n\n                    continue;\n                }\n\n                task.startTaskByRescue();\n            }\n\n            queueHandler.unFreezeSerialQueues(wakeupSerialQueueKeyList);\n        }\n    }\n\n    @Override\n    public void disconnected() {\n\n        if (getConnectStatus() == DownloadServiceConnectChangedEvent.ConnectStatus.lost) {\n\n            final IQueuesHandler queueHandler = FileDownloader.getImpl().getQueuesHandler();\n            // lost the connection to the service\n            if (FileDownloadLog.NEED_LOG) {\n                FileDownloadLog.d(this, \"lost the connection to the \"\n                                + \"file download service, and current active task size is %d\",\n                        FileDownloadList.getImpl().size());\n            }\n\n            if (FileDownloadList.getImpl().size() > 0) {\n                synchronized (mWaitingList) {\n                    FileDownloadList.getImpl().divertAndIgnoreDuplicate(mWaitingList);\n                    for (BaseDownloadTask.IRunningTask task : mWaitingList) {\n                        task.free();\n                    }\n\n                    queueHandler.freezeAllSerialQueues();\n                }\n\n                // start service during the app is in background, the IllegalStateException may be\n                // thrown, but just ignore it is fun.\n                try {\n                    FileDownloader.getImpl().bindService();\n                } catch (IllegalStateException ignored) {\n                    FileDownloadLog.w(this, \"restart service failed, you may need to \"\n                            + \"restart downloading manually when the app comes back to foreground\");\n                }\n            }\n        } else {\n\n            if (FileDownloadList.getImpl().size() > 0) {\n                FileDownloadLog.w(this, \"file download service has be unbound\"\n                                + \" but the size of active tasks are not empty %d \",\n                        FileDownloadList.getImpl().size());\n            }\n        }\n    }\n\n    @Override\n    public boolean isInWaitingList(BaseDownloadTask.IRunningTask task) {\n        return !mWaitingList.isEmpty() && mWaitingList.contains(task);\n    }\n\n    @Override\n    public void taskWorkFine(BaseDownloadTask.IRunningTask task) {\n        if (!mWaitingList.isEmpty()) {\n            synchronized (mWaitingList) {\n                mWaitingList.remove(task);\n            }\n        }\n    }\n\n    @Override\n    public boolean dispatchTaskStart(BaseDownloadTask.IRunningTask task) {\n        if (!FileDownloader.getImpl().isServiceConnected()) {\n            synchronized (mWaitingList) {\n                if (!FileDownloader.getImpl().isServiceConnected()) {\n                    if (FileDownloadLog.NEED_LOG) {\n                        FileDownloadLog.d(this, \"Waiting for connecting with the downloader \"\n                                + \"service... %d\", task.getOrigin().getId());\n                    }\n                    FileDownloadServiceProxy.getImpl().\n                            bindStartByContext(FileDownloadHelper.getAppContext());\n                    if (!mWaitingList.contains(task)) {\n                        task.free();\n                        mWaitingList.add(task);\n                    }\n                    return true;\n                }\n            }\n        }\n\n        taskWorkFine(task);\n\n        return false;\n    }\n}\n"
  },
  {
    "path": "library/src/main/java/com/liulishuo/filedownloader/MessageSnapshotGate.java",
    "content": "/*\n * Copyright (c) 2015 LingoChamp Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *    http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.liulishuo.filedownloader;\n\nimport com.liulishuo.filedownloader.message.MessageSnapshot;\nimport com.liulishuo.filedownloader.message.MessageSnapshotFlow;\nimport com.liulishuo.filedownloader.model.FileDownloadStatus;\nimport com.liulishuo.filedownloader.util.FileDownloadLog;\n\nimport java.util.List;\n\n/**\n * The message snapshot gate beyond the downloader service.\n */\n\npublic class MessageSnapshotGate implements MessageSnapshotFlow.MessageReceiver {\n\n    private boolean transmitMessage(List<BaseDownloadTask.IRunningTask> taskList,\n                                    MessageSnapshot snapshot) {\n\n        if (taskList.size() > 1 && snapshot.getStatus() == FileDownloadStatus.completed) {\n            for (BaseDownloadTask.IRunningTask task : taskList) {\n                synchronized (task.getPauseLock()) {\n                    if (task.getMessageHandler().updateMoreLikelyCompleted(snapshot)) {\n                        FileDownloadLog.d(this, \"updateMoreLikelyCompleted\");\n                        return true;\n                    }\n                }\n            }\n        }\n\n        for (BaseDownloadTask.IRunningTask task : taskList) {\n            synchronized (task.getPauseLock()) {\n                if (task.getMessageHandler().updateKeepFlow(snapshot)) {\n                    FileDownloadLog.d(this, \"updateKeepFlow\");\n                    return true;\n                }\n            }\n        }\n\n        if (FileDownloadStatus.warn == snapshot.getStatus()) {\n            for (BaseDownloadTask.IRunningTask task : taskList) {\n                synchronized (task.getPauseLock()) {\n                    if (task.getMessageHandler().updateSameFilePathTaskRunning(snapshot)) {\n                        FileDownloadLog.d(this, \"updateSampleFilePathTaskRunning\");\n                        return true;\n                    }\n                }\n            }\n        }\n\n        //noinspection SimplifiableIfStatement\n        if (taskList.size() == 1) {\n            // Cover the most case for restarting from the low memory status.\n            final BaseDownloadTask.IRunningTask onlyTask = taskList.get(0);\n            synchronized (onlyTask.getPauseLock()) {\n                FileDownloadLog.d(this, \"updateKeepAhead\");\n                return onlyTask.getMessageHandler().updateKeepAhead(snapshot);\n            }\n        }\n\n        return false;\n    }\n\n    @Override\n    public void receive(MessageSnapshot snapshot) {\n\n        final String updateSyncLock = Integer.toString(snapshot.getId());\n        synchronized (updateSyncLock.intern()) {\n            final List<BaseDownloadTask.IRunningTask> taskList = FileDownloadList.getImpl().\n                    getReceiveServiceTaskList(snapshot.getId());\n\n            if (taskList.size() > 0) {\n                final BaseDownloadTask topOriginTask = taskList.get(0).getOrigin();\n\n                if (FileDownloadLog.NEED_LOG) {\n                    FileDownloadLog.d(this, \"~~~callback %s old[%s] new[%s] %d\",\n                            snapshot.getId(), topOriginTask.getStatus(), snapshot.getStatus(),\n                            taskList.size());\n                }\n\n                if (!transmitMessage(taskList, snapshot)) {\n\n                    StringBuilder log = new StringBuilder(\"The event isn't consumed, id:\"\n                            + snapshot.getId() + \" status:\"\n                            + snapshot.getStatus() + \" task-count:\" + taskList.size());\n                    for (BaseDownloadTask.IRunningTask task : taskList) {\n                        log.append(\" | \").append(task.getOrigin().getStatus());\n                    }\n                    FileDownloadLog.i(this, log.toString());\n                }\n\n\n            } else {\n                FileDownloadLog.i(this, \"Receive the event %d, but there isn't any running\"\n                        + \" task in the upper layer\", snapshot.getStatus());\n            }\n\n        }\n    }\n}\n"
  },
  {
    "path": "library/src/main/java/com/liulishuo/filedownloader/PauseAllMarker.java",
    "content": "/*\n * Copyright (c) 2015 LingoChamp Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *   http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.liulishuo.filedownloader;\n\nimport android.content.Context;\nimport android.os.Handler;\nimport android.os.HandlerThread;\nimport android.os.Message;\nimport android.os.RemoteException;\n\nimport com.liulishuo.filedownloader.i.IFileDownloadIPCService;\nimport com.liulishuo.filedownloader.util.FileDownloadHelper;\nimport com.liulishuo.filedownloader.util.FileDownloadLog;\n\nimport java.io.File;\nimport java.io.IOException;\n\npublic class PauseAllMarker implements Handler.Callback {\n\n    private static final String MAKER_FILE_NAME = \".filedownloader_pause_all_marker.b\";\n    private static File markerFile;\n    private static final Long PAUSE_ALL_CHECKER_PERIOD = 1000L; // 1 second\n    private static final int PAUSE_ALL_CHECKER_WHAT = 0;\n    private HandlerThread pauseAllChecker;\n    private Handler pauseAllHandler;\n    private final IFileDownloadIPCService serviceHandler;\n\n    public PauseAllMarker(IFileDownloadIPCService serviceHandler) {\n        this.serviceHandler = serviceHandler;\n    }\n\n    @SuppressWarnings(\"ResultOfMethodCallIgnored\")\n    public static void createMarker() {\n        final File markerFile = markerFile();\n        if (!markerFile.getParentFile().exists()) markerFile.getParentFile().mkdirs();\n        if (markerFile.exists()) {\n            FileDownloadLog.w(PauseAllMarker.class, \"marker file \" + markerFile.getAbsolutePath()\n                    + \" exists\");\n            return;\n        }\n        try {\n            boolean success = markerFile.createNewFile();\n            FileDownloadLog.d(PauseAllMarker.class, \"create marker file\"\n                    + markerFile.getAbsolutePath() + \" \" + success);\n        } catch (IOException e) {\n            FileDownloadLog.e(PauseAllMarker.class, \"create marker file failed\", e);\n        }\n    }\n\n    private static File markerFile() {\n        if (markerFile == null) {\n            final Context context = FileDownloadHelper.getAppContext();\n            markerFile = new File(context.getCacheDir() + File.separator + MAKER_FILE_NAME);\n        }\n        return markerFile;\n    }\n\n    private static boolean isMarked() {\n        return markerFile().exists();\n    }\n\n    @SuppressWarnings(\"ResultOfMethodCallIgnored\")\n    public static void clearMarker() {\n        final File file = markerFile();\n        if (file.exists()) {\n            FileDownloadLog.d(PauseAllMarker.class, \"delete marker file \" + file.delete());\n        }\n    }\n\n    public void startPauseAllLooperCheck() {\n        pauseAllChecker = new HandlerThread(\"PauseAllChecker\");\n        pauseAllChecker.start();\n        pauseAllHandler = new Handler(pauseAllChecker.getLooper(), this);\n        pauseAllHandler.sendEmptyMessageDelayed(PAUSE_ALL_CHECKER_WHAT, PAUSE_ALL_CHECKER_PERIOD);\n    }\n\n    public void stopPauseAllLooperCheck() {\n        pauseAllHandler.removeMessages(PAUSE_ALL_CHECKER_WHAT);\n        pauseAllChecker.quit();\n    }\n\n\n    @Override\n    public boolean handleMessage(Message msg) {\n        if (PauseAllMarker.isMarked()) {\n            try {\n                serviceHandler.pauseAllTasks();\n            } catch (RemoteException e) {\n                FileDownloadLog.e(this, e, \"pause all failed\");\n            } finally {\n                PauseAllMarker.clearMarker();\n            }\n        }\n        pauseAllHandler.sendEmptyMessageDelayed(0, PAUSE_ALL_CHECKER_PERIOD);\n        return true;\n    }\n}\n"
  },
  {
    "path": "library/src/main/java/com/liulishuo/filedownloader/QueuesHandler.java",
    "content": "/*\n * Copyright (c) 2015 LingoChamp Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *    http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.liulishuo.filedownloader;\n\nimport android.os.Handler;\nimport android.os.HandlerThread;\nimport android.os.Message;\nimport android.util.SparseArray;\n\nimport com.liulishuo.filedownloader.model.FileDownloadStatus;\nimport com.liulishuo.filedownloader.util.FileDownloadLog;\nimport com.liulishuo.filedownloader.util.FileDownloadUtils;\n\nimport java.lang.ref.WeakReference;\nimport java.util.List;\n\n/**\n * The default queue handler.\n */\n\nclass QueuesHandler implements IQueuesHandler {\n\n    private final SparseArray<Handler> mRunningSerialMap;\n\n    QueuesHandler() {\n        this.mRunningSerialMap = new SparseArray<>();\n    }\n\n    @Override\n    public boolean startQueueParallel(FileDownloadListener listener) {\n        final int attachKey = listener.hashCode();\n\n        final List<BaseDownloadTask.IRunningTask> list = FileDownloadList.getImpl().\n                assembleTasksToStart(attachKey, listener);\n\n        if (onAssembledTasksToStart(attachKey, list, listener, false)) {\n            return false;\n        }\n\n        for (BaseDownloadTask.IRunningTask task : list) {\n            task.startTaskByQueue();\n        }\n\n        return true;\n    }\n\n    @Override\n    public boolean startQueueSerial(FileDownloadListener listener) {\n        final SerialHandlerCallback callback = new SerialHandlerCallback();\n        final int attachKey = callback.hashCode();\n\n        final List<BaseDownloadTask.IRunningTask> list = FileDownloadList.getImpl().\n                assembleTasksToStart(attachKey, listener);\n\n        if (onAssembledTasksToStart(attachKey, list, listener, true)) {\n            return false;\n        }\n\n        final HandlerThread serialThread = new HandlerThread(\n                FileDownloadUtils.formatString(\"filedownloader serial thread %s-%d\",\n                        listener, attachKey));\n        serialThread.start();\n\n        final Handler serialHandler = new Handler(serialThread.getLooper(), callback);\n        callback.setHandler(serialHandler);\n        callback.setList(list);\n\n        callback.goNext(0);\n\n        synchronized (mRunningSerialMap) {\n            mRunningSerialMap.put(attachKey, serialHandler);\n        }\n\n        return true;\n    }\n\n    @Override\n    public void freezeAllSerialQueues() {\n        for (int i = 0; i < mRunningSerialMap.size(); i++) {\n            final int key = mRunningSerialMap.keyAt(i);\n            Handler handler = mRunningSerialMap.get(key);\n            freezeSerialHandler(handler);\n        }\n    }\n\n    @Override\n    public void unFreezeSerialQueues(List<Integer> attachKeyList) {\n        for (Integer attachKey : attachKeyList) {\n            final Handler handler = mRunningSerialMap.get(attachKey);\n            unFreezeSerialHandler(handler);\n        }\n    }\n\n    @Override\n    public int serialQueueSize() {\n        return mRunningSerialMap.size();\n    }\n\n    @Override\n    public boolean contain(int attachKey) {\n        return mRunningSerialMap.get(attachKey) != null;\n    }\n\n    private boolean onAssembledTasksToStart(int attachKey,\n                                            final List<BaseDownloadTask.IRunningTask> list,\n                                            final FileDownloadListener listener, boolean isSerial) {\n        if (FileDownloadMonitor.isValid()) {\n            FileDownloadMonitor.getMonitor().onRequestStart(list.size(), true, listener);\n        }\n\n        if (FileDownloadLog.NEED_LOG) {\n            FileDownloadLog.v(FileDownloader.class, \"start list attachKey[%d] size[%d] \"\n                    + \"listener[%s] isSerial[%B]\", attachKey, list.size(), listener, isSerial);\n        }\n\n        if (list == null || list.isEmpty()) {\n            FileDownloadLog.w(FileDownloader.class, \"Tasks with the listener can't start, \"\n                            + \"because can't find any task with the provided listener, maybe tasks \"\n                            + \"instance has been started in the past, so they are all are inUsing, \"\n                            + \"if in this case, you can use [BaseDownloadTask#reuse] to reuse theme\"\n                            + \" first then start again: [%s, %B]\",\n                    listener, isSerial);\n\n            return true;\n        }\n\n        return false;\n\n    }\n\n\n    static final int WHAT_SERIAL_NEXT = 1;\n    static final int WHAT_FREEZE = 2;\n    static final int WHAT_UNFREEZE = 3;\n\n\n    private class SerialHandlerCallback implements Handler.Callback {\n        private Handler mHandler;\n        private List<BaseDownloadTask.IRunningTask> mList;\n        private int mRunningIndex = 0;\n        private SerialFinishListener mSerialFinishListener;\n\n        SerialHandlerCallback() {\n            mSerialFinishListener =\n                    new SerialFinishListener(new WeakReference<>(this));\n        }\n\n        public void setHandler(final Handler handler) {\n            this.mHandler = handler;\n        }\n\n        public void setList(List<BaseDownloadTask.IRunningTask> list) {\n            this.mList = list;\n        }\n\n        @Override\n        public boolean handleMessage(final Message msg) {\n            if (msg.what == WHAT_SERIAL_NEXT) {\n                if (msg.arg1 >= mList.size()) {\n                    synchronized (mRunningSerialMap) {\n                        mRunningSerialMap.remove(mList.get(0).getAttachKey());\n                    }\n                    // final serial tasks\n                    if (this.mHandler != null && this.mHandler.getLooper() != null) {\n                        this.mHandler.getLooper().quit();\n                        this.mHandler = null;\n                        this.mList = null;\n                        this.mSerialFinishListener = null;\n                    }\n\n                    if (FileDownloadLog.NEED_LOG) {\n                        FileDownloadLog.d(SerialHandlerCallback.class, \"final serial %s %d\",\n                                this.mList == null ? null : this.mList.get(0) == null\n                                        ? null : this.mList.get(0).getOrigin().getListener(),\n                                msg.arg1);\n                    }\n                    return true;\n                }\n\n                mRunningIndex = msg.arg1;\n                final BaseDownloadTask.IRunningTask stackTopTask = this.mList.get(mRunningIndex);\n                synchronized (stackTopTask.getPauseLock()) {\n                    if (stackTopTask.getOrigin().getStatus() != FileDownloadStatus.INVALID_STATUS\n                            || FileDownloadList.getImpl().isNotContains(stackTopTask)) {\n                        // pause?\n                        if (FileDownloadLog.NEED_LOG) {\n                            FileDownloadLog.d(SerialHandlerCallback.class,\n                                    \"direct go next by not contains %s %d\", stackTopTask, msg.arg1);\n                        }\n                        goNext(msg.arg1 + 1);\n                        return true;\n                    }\n\n                    stackTopTask.getOrigin()\n                            .addFinishListener(\n                                    mSerialFinishListener.setNextIndex(mRunningIndex + 1));\n                    stackTopTask.startTaskByQueue();\n                }\n\n            } else if (msg.what == WHAT_FREEZE) {\n                freeze();\n            } else if (msg.what == WHAT_UNFREEZE) {\n                unfreeze();\n            }\n            return true;\n        }\n\n        public void freeze() {\n            mList.get(mRunningIndex).getOrigin().removeFinishListener(mSerialFinishListener);\n            mHandler.removeCallbacksAndMessages(null);\n        }\n\n        public void unfreeze() {\n            goNext(mRunningIndex);\n        }\n\n        private void goNext(final int nextIndex) {\n            if (this.mHandler == null || this.mList == null) {\n                FileDownloadLog.w(this, \"need go next %d, but params is not ready %s %s\",\n                        nextIndex, this.mHandler, this.mList);\n                return;\n            }\n\n            Message nextMsg = this.mHandler.obtainMessage();\n            nextMsg.what = WHAT_SERIAL_NEXT;\n            nextMsg.arg1 = nextIndex;\n            if (FileDownloadLog.NEED_LOG) {\n                FileDownloadLog.d(SerialHandlerCallback.class, \"start next %s %s\",\n                        this.mList == null ? null : this.mList.get(0) == null ? null\n                                : this.mList.get(0).getOrigin().getListener(), nextMsg.arg1);\n            }\n            this.mHandler.sendMessage(nextMsg);\n        }\n    }\n\n    private static class SerialFinishListener implements BaseDownloadTask.FinishListener {\n        private final WeakReference<SerialHandlerCallback> wSerialHandlerCallback;\n\n        private SerialFinishListener(WeakReference<SerialHandlerCallback> wSerialHandlerCallback) {\n            this.wSerialHandlerCallback = wSerialHandlerCallback;\n        }\n\n        private int nextIndex;\n\n        public BaseDownloadTask.FinishListener setNextIndex(int index) {\n            this.nextIndex = index;\n            return this;\n        }\n\n        @Override\n        public void over(final BaseDownloadTask task) {\n            if (wSerialHandlerCallback != null && wSerialHandlerCallback.get() != null) {\n                wSerialHandlerCallback.get().goNext(this.nextIndex);\n            }\n        }\n    }\n\n    private void freezeSerialHandler(Handler handler) {\n        handler.sendEmptyMessage(WHAT_FREEZE);\n    }\n\n    private void unFreezeSerialHandler(Handler handler) {\n        handler.sendEmptyMessage(WHAT_UNFREEZE);\n    }\n}\n"
  },
  {
    "path": "library/src/main/java/com/liulishuo/filedownloader/connection/DefaultConnectionCountAdapter.java",
    "content": "/*\n * Copyright (c) 2015 LingoChamp Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *   http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.liulishuo.filedownloader.connection;\n\nimport com.liulishuo.filedownloader.util.FileDownloadHelper;\n\n/**\n * The default connection count adapter.\n */\n\npublic class DefaultConnectionCountAdapter implements FileDownloadHelper.ConnectionCountAdapter {\n\n    // 1 connection: [0, 1MB)\n    private static final long ONE_CONNECTION_UPPER_LIMIT = 1024 * 1024; // 1MB\n    // 2 connection: [1MB, 5MB)\n    private static final long TWO_CONNECTION_UPPER_LIMIT = 5 * 1024 * 1024; // 5MB\n    // 3 connection: [5MB, 50MB)\n    private static final long THREE_CONNECTION_UPPER_LIMIT = 50 * 1024 * 1024; // 50MB\n    // 4 connection: [50MB, 100MB)\n    private static final long FOUR_CONNECTION_UPPER_LIMIT = 100 * 1024 * 1024; // 100MB\n\n    @Override\n    public int determineConnectionCount(int downloadId, String url, String path, long totalLength) {\n        if (totalLength < ONE_CONNECTION_UPPER_LIMIT) {\n            return 1;\n        }\n\n        if (totalLength < TWO_CONNECTION_UPPER_LIMIT) {\n            return 2;\n        }\n\n        if (totalLength < THREE_CONNECTION_UPPER_LIMIT) {\n            return 3;\n        }\n\n        if (totalLength < FOUR_CONNECTION_UPPER_LIMIT) {\n            return 4;\n        }\n\n        return 5;\n    }\n}\n"
  },
  {
    "path": "library/src/main/java/com/liulishuo/filedownloader/connection/FileDownloadConnection.java",
    "content": "/*\n * Copyright (c) 2015 LingoChamp Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *   http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.liulishuo.filedownloader.connection;\n\nimport java.io.IOException;\nimport java.io.InputStream;\nimport java.net.ProtocolException;\nimport java.net.URLConnection;\nimport java.util.List;\nimport java.util.Map;\n\n/**\n * The connection used for connecting to the network.\n */\n\n@SuppressWarnings(\"EmptyMethod\")\npublic interface FileDownloadConnection {\n    int NO_RESPONSE_CODE = 0;\n    int RESPONSE_CODE_FROM_OFFSET = 1;\n\n    /**\n     * Sets the header named {@code name} to {@code value}.\n     * <p>\n     * The capacity of this method is similar to the\n     * {@link URLConnection#addRequestProperty(String, String)}\n     */\n    void addHeader(String name, String value);\n\n    /**\n     * If we find the file has been downloaded several bytes, we will try to resume from the\n     * breakpoint from {@code offset} length.\n     *\n     * @param etag   the etag is stored by the past downloaded.\n     * @param offset the offset length has already been downloaded.\n     * @return {@code true} if adding resume offset was dispatched, so we can't handle that by\n     * internal.\n     */\n    @SuppressWarnings(\"UnusedParameters\")\n    boolean dispatchAddResumeOffset(String etag, long offset);\n\n    /**\n     * Returns an input stream that reads from this open connection.\n     * <p>\n     * The capacity of this method is similar to the {@link URLConnection#getInputStream()}\n     *\n     * @return an input stream that reads from this open connection.\n     */\n    InputStream getInputStream() throws IOException;\n\n    /**\n     * Returns an unmodifiable Map of general request header fields for this connection. The Map\n     * keys are Strings that represent the request-header field names. Each Map value is a\n     * unmodifiable List of Strings that represents the corresponding field values.\n     * <p>\n     * The capacity of this method is similar to the {@link URLConnection#getRequestProperties()}\n     *\n     * @return a Map of the general request properties for this connection.\n     */\n    Map<String, List<String>> getRequestHeaderFields();\n\n    /**\n     * Returns an unmodifiable Map of the header fields. The Map keys are Strings that represent\n     * the response-header field names. Each Map value is an unmodifiable List of Strings that\n     * represents the corresponding field values.\n     * <p>\n     * The capacity of this method is similar to the {@link URLConnection#getHeaderFields()}\n     *\n     * @return a Map of header fields\n     */\n    Map<String, List<String>> getResponseHeaderFields();\n\n    /**\n     * Returns the value of the named header field, which would be the response-header field.\n     * <p>\n     * If called on a connection that sets the same header multiple times\n     * with possibly different values, only the last value is returned.\n     *\n     * @param name the name of a header field.\n     * @return the value of the named header field, or <code>null</code>\n     * if there is no such field in the header.\n     */\n    String getResponseHeaderField(String name);\n\n    /**\n     * Set the method for the request, one of:\n     * <UL>\n     *  <LI>GET\n     *  <LI>POST\n     *  <LI>HEAD\n     *  <LI>OPTIONS\n     *  <LI>PUT\n     *  <LI>DELETE\n     *  <LI>TRACE\n     * </UL> are legal, subject to protocol restrictions.  The default\n     * method is GET.\n     *\n     * @param method the HTTP method\n     * @exception ProtocolException if the method cannot be reset or if\n     *              the requested method isn't valid for HTTP.\n     * @exception SecurityException if a security manager is set and the\n     *              method is \"TRACE\", but the \"allowHttpTrace\"\n     *              NetPermission is not granted.\n     *\n     * @return {@code true} if set effect, otherwise {@code false}.\n     */\n    boolean setRequestMethod(String method) throws ProtocolException;\n\n    /**\n     * Invokes the request immediately, and blocks until the response can be processed or is in\n     * error.\n     */\n    void execute() throws IOException;\n\n    /**\n     * Gets the status code from an HTTP response message.\n     * <p>\n     * <strong>If this is not http/https protocol connection</strong>:\n     * 1. If you make sure this connection is resume from the offset breakpoint(which you can check\n     * out this through {@link #dispatchAddResumeOffset(String, long)}), please return\n     * {@link #RESPONSE_CODE_FROM_OFFSET}\n     * 2. otherwise, return {@link #NO_RESPONSE_CODE}.\n     *\n     * @return the HTTP Status-Code, or -1\n     */\n    int getResponseCode() throws IOException;\n\n    /**\n     * To Be Reused or Close this connection, since this connection is ending in this session.\n     */\n    void ending();\n}\n"
  },
  {
    "path": "library/src/main/java/com/liulishuo/filedownloader/connection/FileDownloadUrlConnection.java",
    "content": "/*\n * Copyright (c) 2015 LingoChamp Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *   http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.liulishuo.filedownloader.connection;\n\nimport com.liulishuo.filedownloader.util.FileDownloadHelper;\n\nimport java.io.IOException;\nimport java.io.InputStream;\nimport java.net.HttpURLConnection;\nimport java.net.ProtocolException;\nimport java.net.Proxy;\nimport java.net.URL;\nimport java.net.URLConnection;\nimport java.util.List;\nimport java.util.Map;\n\n/**\n * The FileDownloadConnection implemented using {@link URLConnection}.\n */\n\npublic class FileDownloadUrlConnection implements FileDownloadConnection {\n    protected URLConnection mConnection;\n\n    public FileDownloadUrlConnection(String originUrl, Configuration configuration)\n            throws IOException {\n        this(new URL(originUrl), configuration);\n    }\n\n    public FileDownloadUrlConnection(URL url, Configuration configuration) throws IOException {\n        if (configuration != null && configuration.proxy != null) {\n            mConnection = url.openConnection(configuration.proxy);\n        } else {\n            mConnection = url.openConnection();\n        }\n        if (mConnection instanceof HttpURLConnection) {\n            ((HttpURLConnection) mConnection).setInstanceFollowRedirects(false);\n        }\n\n        if (configuration != null) {\n            if (configuration.readTimeout != null) {\n                mConnection.setReadTimeout(configuration.readTimeout);\n            }\n\n            if (configuration.connectTimeout != null) {\n                mConnection.setConnectTimeout(configuration.connectTimeout);\n            }\n        }\n    }\n\n    public FileDownloadUrlConnection(String originUrl) throws IOException {\n        this(originUrl, null);\n    }\n\n    @Override\n    public void addHeader(String name, String value) {\n        mConnection.addRequestProperty(name, value);\n    }\n\n    @Override\n    public boolean dispatchAddResumeOffset(String etag, long offset) {\n        return false;\n    }\n\n    @Override\n    public InputStream getInputStream() throws IOException {\n        return mConnection.getInputStream();\n    }\n\n    @Override\n    public Map<String, List<String>> getRequestHeaderFields() {\n        return mConnection.getRequestProperties();\n    }\n\n    @Override\n    public Map<String, List<String>> getResponseHeaderFields() {\n        return mConnection.getHeaderFields();\n    }\n\n    @Override\n    public String getResponseHeaderField(String name) {\n        return mConnection.getHeaderField(name);\n    }\n\n    @Override public boolean setRequestMethod(String method) throws ProtocolException {\n        if (mConnection instanceof HttpURLConnection) {\n            ((HttpURLConnection) mConnection).setRequestMethod(method);\n            return true;\n        }\n\n        return false;\n    }\n\n    @Override\n    public void execute() throws IOException {\n        mConnection.connect();\n    }\n\n    @Override\n    public int getResponseCode() throws IOException {\n        if (mConnection instanceof HttpURLConnection) {\n            return ((HttpURLConnection) mConnection).getResponseCode();\n        }\n\n        return FileDownloadConnection.NO_RESPONSE_CODE;\n    }\n\n    @Override\n    public void ending() {\n        try {\n            mConnection.getInputStream().close();\n        } catch (IOException ignored) {\n        }\n    }\n\n\n    public static class Creator implements FileDownloadHelper.ConnectionCreator {\n        private final Configuration mConfiguration;\n\n        public Creator() {\n            this(null);\n        }\n\n        public Creator(Configuration configuration) {\n            this.mConfiguration = configuration;\n        }\n\n        FileDownloadConnection create(URL url) throws IOException {\n            return new FileDownloadUrlConnection(url, mConfiguration);\n        }\n\n        @Override\n        public FileDownloadConnection create(String originUrl) throws IOException {\n            return new FileDownloadUrlConnection(originUrl, mConfiguration);\n        }\n    }\n\n    /**\n     * The sample configuration for the {@link FileDownloadUrlConnection}\n     */\n    public static class Configuration {\n        private Proxy proxy;\n        private Integer readTimeout;\n        private Integer connectTimeout;\n\n        /**\n         * The connection will be made through the specified proxy.\n         * <p>\n         * This {@code proxy} will be used when invoke {@link URL#openConnection(Proxy)}\n         *\n         * @param proxy the proxy will be applied to the {@link FileDownloadUrlConnection}\n         */\n        public Configuration proxy(Proxy proxy) {\n            this.proxy = proxy;\n            return this;\n        }\n\n        /**\n         * Sets the read timeout to a specified timeout, in milliseconds. A non-zero value specifies\n         * the timeout when reading from Input stream when a connection is established to a resource\n         * <p>\n         * If the timeout expires before there is data available for read, a\n         * java.net.SocketTimeoutException is raised. A timeout of zero is interpreted as an\n         * infinite timeout.\n         * <p>\n         * This {@code readTimeout} will be applied through\n         * {@link URLConnection#setReadTimeout(int)}\n         *\n         * @param readTimeout an <code>int</code> that specifies the timeout value to be used in\n         *                    milliseconds\n         */\n        public Configuration readTimeout(int readTimeout) {\n            this.readTimeout = readTimeout;\n            return this;\n        }\n\n        /**\n         * Sets a specified timeout value, in milliseconds, to be used when opening a communications\n         * link to the resource referenced by this URLConnection.  If the timeout expires before the\n         * connection can be established, a java.net.SocketTimeoutException is raised. A timeout of\n         * zero is interpreted as an infinite timeout.\n         * <p>\n         * This {@code connectionTimeout} will be applied through\n         * {@link URLConnection#setConnectTimeout(int)}\n         *\n         * @param connectTimeout an <code>int</code> that specifies the connect timeout value in\n         *                       milliseconds\n         */\n        public Configuration connectTimeout(int connectTimeout) {\n            this.connectTimeout = connectTimeout;\n            return this;\n        }\n\n\n    }\n}\n"
  },
  {
    "path": "library/src/main/java/com/liulishuo/filedownloader/connection/RedirectHandler.java",
    "content": "/*\n * Copyright (c) 2015 LingoChamp Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *   http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.liulishuo.filedownloader.connection;\n\nimport com.liulishuo.filedownloader.download.CustomComponentHolder;\nimport com.liulishuo.filedownloader.util.FileDownloadLog;\nimport com.liulishuo.filedownloader.util.FileDownloadUtils;\n\nimport java.io.IOException;\nimport java.net.HttpURLConnection;\nimport java.util.ArrayList;\nimport java.util.List;\nimport java.util.Map;\nimport java.util.Set;\n\n/**\n * Handle redirect case.\n */\npublic class RedirectHandler {\n\n    private static final int MAX_REDIRECT_TIMES = 10;\n\n    /**\n     * The target resource resides temporarily under a different URI and the user agent MUST NOT\n     * change the request method if it performs an automatic redirection to that URI.\n     */\n    private static final int HTTP_TEMPORARY_REDIRECT = 307;\n    /**\n     * The target resource has been assigned a new permanent URI and any future references to this\n     * resource ought to use one of the enclosed URIs.\n     */\n    private static final int HTTP_PERMANENT_REDIRECT = 308;\n\n\n    public static FileDownloadConnection process(\n            final Map<String, List<String>> requestHeaderFields,\n            final FileDownloadConnection connection,\n            List<String> redirectedUrlList)\n            throws IOException, IllegalAccessException {\n\n        int code = connection.getResponseCode();\n        String location = connection.getResponseHeaderField(\"Location\");\n\n        List<String> redirectLocationList = new ArrayList<>();\n        int redirectTimes = 0;\n        FileDownloadConnection redirectConnection = connection;\n\n        while (isRedirect(code)) {\n            if (location == null) {\n                throw new IllegalAccessException(FileDownloadUtils.\n                        formatString(\n                                \"receive %d (redirect) but the location is null with response [%s]\",\n                                code, redirectConnection.getResponseHeaderFields()));\n            }\n\n            if (FileDownloadLog.NEED_LOG) {\n                FileDownloadLog.d(RedirectHandler.class, \"redirect to %s with %d, %s\",\n                        location, code, redirectLocationList);\n            }\n\n            redirectConnection.ending();\n            redirectConnection =\n                    buildRedirectConnection(requestHeaderFields, location);\n            redirectLocationList.add(location);\n\n            redirectConnection.execute();\n            code = redirectConnection.getResponseCode();\n            location = redirectConnection.getResponseHeaderField(\"Location\");\n\n            if (++redirectTimes >= MAX_REDIRECT_TIMES) {\n                throw new IllegalAccessException(\n                        FileDownloadUtils\n                                .formatString(\"redirect too many times! %s\", redirectLocationList));\n            }\n        }\n\n        if (redirectedUrlList != null) {\n            redirectedUrlList.addAll(redirectLocationList);\n        }\n\n        return redirectConnection;\n    }\n\n    private static boolean isRedirect(int code) {\n        return code == HttpURLConnection.HTTP_MOVED_PERM\n                || code == HttpURLConnection.HTTP_MOVED_TEMP\n                || code == HttpURLConnection.HTTP_SEE_OTHER\n                || code == HttpURLConnection.HTTP_MULT_CHOICE\n                || code == HTTP_TEMPORARY_REDIRECT\n                || code == HTTP_PERMANENT_REDIRECT;\n    }\n\n    private static FileDownloadConnection buildRedirectConnection(\n            Map<String, List<String>> requestHeaderFields,\n            String newUrl) throws IOException {\n        FileDownloadConnection redirectConnection = CustomComponentHolder.getImpl().\n                createConnection(newUrl);\n\n        String name;\n        List<String> list;\n\n        Set<Map.Entry<String, List<String>>> entries = requestHeaderFields.entrySet();\n        for (Map.Entry<String, List<String>> e : entries) {\n            name = e.getKey();\n            list = e.getValue();\n            if (list != null) {\n                for (String value : list) {\n                    redirectConnection.addHeader(name, value);\n                }\n            }\n        }\n\n        return redirectConnection;\n    }\n}\n"
  },
  {
    "path": "library/src/main/java/com/liulishuo/filedownloader/database/FileDownloadDatabase.java",
    "content": "/*\n * Copyright (c) 2015 LingoChamp Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *   http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.liulishuo.filedownloader.database;\n\n\nimport com.liulishuo.filedownloader.model.ConnectionModel;\nimport com.liulishuo.filedownloader.model.FileDownloadModel;\nimport com.liulishuo.filedownloader.model.FileDownloadStatus;\nimport com.liulishuo.filedownloader.util.FileDownloadUtils;\n\nimport java.util.List;\n\n/**\n * The filedownloader database, what is used for storing the {@link FileDownloadModel}.\n * <p/>\n * The data stored in the database is only used for task resumes from the breakpoint.\n * <p>\n * The task of the data stored in the database must be a task that has not finished downloading yet,\n * and if the task has finished downloading, its data will be {@link #remove(int)} from the\n * database, since that data is no longer available for resumption of its task pass.\n *\n * @see SqliteDatabaseImpl\n * @see FileDownloadUtils#isBreakpointAvailable(int, FileDownloadModel)\n */\n@SuppressWarnings(\"UnusedParameters\")\npublic interface FileDownloadDatabase {\n\n    /**\n     * Invoked when task is started.\n     *\n     * @param id the download id.\n     */\n    void onTaskStart(final int id);\n\n    /**\n     * Find the model which identify is {@code id}.\n     *\n     * @param id the download id.\n     */\n    FileDownloadModel find(final int id);\n\n    /**\n     * Find the connection model which download identify is {@code id}\n     *\n     * @param id the download id.\n     */\n    List<ConnectionModel> findConnectionModel(int id);\n\n    /**\n     * Delete all connection model store on database through the download id.\n     *\n     * @param id the download id.\n     */\n    void removeConnections(int id);\n\n    /**\n     * Insert the {@code model} to connection table.\n     *\n     * @param model the connection model.\n     */\n    void insertConnectionModel(ConnectionModel model);\n\n    /**\n     * Update the currentOffset with {@code currentOffset} which id is {@code id}, index is\n     * {@code index}\n     *\n     * @param id            the download id.\n     * @param index         the connection index.\n     * @param currentOffset the current offset.\n     */\n    void updateConnectionModel(int id, int index, long currentOffset);\n\n    /**\n     * Update the count of connection.\n     *\n     * @param count the connection count.\n     */\n    void updateConnectionCount(int id, int count);\n\n    /**\n     * Insert the model to the database.\n     *\n     * @param downloadModel the download model.\n     */\n    void insert(final FileDownloadModel downloadModel);\n\n    /**\n     * Update the data compare to the {@code downloadModel}\n     *\n     * @param downloadModel the download model.\n     */\n    void update(final FileDownloadModel downloadModel);\n\n    /**\n     * Remove the model which identify is {@code id}.\n     *\n     * @param id the download id.\n     * @return {@code true} if succeed to remove model from the database.\n     */\n    boolean remove(final int id);\n\n    /**\n     * Clear all models in this database.\n     */\n    void clear();\n\n\n    /**\n     * Update when the old one is overdue.\n     */\n    void updateOldEtagOverdue(int id, String newEtag, long sofar, long total, int connectionCount);\n\n    /**\n     * Update the data because of the download status alternative to\n     * {@link FileDownloadStatus#connected}.\n     *\n     * @param id       the download id.\n     * @param total    the new total bytes.\n     * @param etag     the new etag. this value will be {@code null} when we can't find it on\n     *                 response header.\n     * @param filename the new file name. this value will be {@code null} when its no need to store.\n     */\n    void updateConnected(int id, long total, String etag, String filename);\n\n    /**\n     * Update the sofar bytes with the status {@code progress}, so don't forget to store the\n     * {@link FileDownloadStatus#progress} too.\n     *\n     * @param sofarBytes the current sofar bytes.\n     */\n    void updateProgress(int id, long sofarBytes);\n\n    /**\n     * Update the data because of the download status alternative to\n     * {@link FileDownloadStatus#error}.\n     *\n     * @param id        the download id.\n     * @param throwable the new exception.\n     * @param sofar     the new so far bytes.\n     */\n    void updateError(int id, Throwable throwable, long sofar);\n\n    /**\n     * Update the data because of the download status alternative to\n     * {@link FileDownloadStatus#retry}.\n     *\n     * @param id        the download id.\n     * @param throwable the new exception.\n     */\n    void updateRetry(int id, Throwable throwable);\n\n    /**\n     * Update the data because of the download status alternative to\n     * {@link FileDownloadStatus#completed}.\n     * The latest version will remove model from DB.\n     *\n     * @param id    the download id.\n     * @param total the new total bytes.\n     */\n    void updateCompleted(int id, final long total);\n\n    /**\n     * Update the data because of the download status alternative to\n     * {@link FileDownloadStatus#paused}.\n     *\n     * @param id    the download id.\n     * @param sofar the new so far bytes.\n     */\n    void updatePause(int id, final long sofar);\n\n    /**\n     * Update the data because of the download status alternative to\n     * {@link FileDownloadStatus#pending}.\n     *\n     * @param id the download id.\n     */\n    void updatePending(int id);\n\n    /**\n     * Get the maintainer for the database, this maintainer will be used when the database is\n     * initializing.\n     * <p>\n     * The maintainer will return all data on the database.\n     * <p>\n     * Demo: {@link SqliteDatabaseImpl.Maintainer}\n     *\n     * @return the maintainer for maintain the database.\n     */\n    Maintainer maintainer();\n\n    /**\n     * the maintainer for the database, this maintainer will be used when the database is\n     * initializing.\n     */\n    @SuppressWarnings(\"EmptyMethod\")\n    interface Maintainer extends Iterable<FileDownloadModel> {\n        /**\n         * invoke this method when the operation for maintain is finished.\n         */\n        void onFinishMaintain();\n\n        /**\n         * invoke this method when the {@code model} is invalid and has been removed.\n         *\n         * @param model the removed invalid model.\n         */\n        void onRemovedInvalidData(FileDownloadModel model);\n\n        /**\n         * invoke this method when the {@code model} is valid to save and has been refreshed.\n         *\n         * @param model the refreshed valid model.\n         */\n        void onRefreshedValidData(FileDownloadModel model);\n\n        /**\n         * invoke this method when the {@link FileDownloadModel#id} is changed because of the\n         * different {@link com.liulishuo.filedownloader.util.FileDownloadHelper.IdGenerator},\n         * which generate the new id for the task.\n         * <p>\n         * tips: you need to update the filedownloader-table and the connection-table.\n         *\n         * @param oldId          the old id for the {@code modelWithNewId}\n         * @param modelWithNewId the model with the new id.\n         */\n        void changeFileDownloadModelId(int oldId, FileDownloadModel modelWithNewId);\n    }\n}\n"
  },
  {
    "path": "library/src/main/java/com/liulishuo/filedownloader/database/NoDatabaseImpl.java",
    "content": "/*\n * Copyright (c) 2015 LingoChamp Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *   http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.liulishuo.filedownloader.database;\n\nimport android.util.SparseArray;\n\nimport com.liulishuo.filedownloader.model.ConnectionModel;\nimport com.liulishuo.filedownloader.model.FileDownloadModel;\nimport com.liulishuo.filedownloader.util.FileDownloadHelper;\nimport com.liulishuo.filedownloader.util.FileDownloadLog;\n\nimport java.util.ArrayList;\nimport java.util.Iterator;\nimport java.util.List;\n\n/**\n * The no database implementation, this implementation no use database anymore, it just store the\n * data on the cache, which means when the process is killed or re-create everything would be gone\n * including all breakpoint data, and some mistake rescue will be can't be achieve.\n * <p>\n * but you can get following benefit:\n * <p>\n * 1. there isn't any database error anymore.\n * 2. if the process is alive the breakpoint is store on the cache, so the breakpoint is valid which\n * produce on the process living time.\n * 3. there isn't any cost from database work.\n * <p>\n * You can valid this database implementation through:\n * <p>\n * class MyApplication extends Application {\n *     ...\n *     public void onCreate() {\n *          ...\n *          FileDownloader.setupOnApplicationOnCreate(this)\n *              .database(NoDatabaseImpl.createMaker())\n *              ...\n *              .commit();\n *          ...\n *     }\n *     ...\n * }\n */\npublic class NoDatabaseImpl implements FileDownloadDatabase {\n\n    final SparseArray<FileDownloadModel> downloaderModelMap = new SparseArray<>();\n    final SparseArray<List<ConnectionModel>> connectionModelListMap = new SparseArray<>();\n\n    public NoDatabaseImpl() {\n    }\n\n    public static Maker createMaker() {\n        return new Maker();\n    }\n\n    @Override\n    public void onTaskStart(int id) {\n    }\n\n    @Override\n    public FileDownloadModel find(final int id) {\n        synchronized (downloaderModelMap) {\n            return downloaderModelMap.get(id);\n        }\n    }\n\n    @Override\n    public List<ConnectionModel> findConnectionModel(int id) {\n        final List<ConnectionModel> resultList = new ArrayList<>();\n        List<ConnectionModel> processList = null;\n        synchronized (connectionModelListMap) {\n            processList = connectionModelListMap.get(id);\n        }\n        if (processList != null) resultList.addAll(processList);\n        return resultList;\n    }\n\n    @Override\n    public void removeConnections(int id) {\n        synchronized (connectionModelListMap) {\n            connectionModelListMap.remove(id);\n        }\n    }\n\n    @Override\n    public void insertConnectionModel(ConnectionModel model) {\n        final int id = model.getId();\n        synchronized (connectionModelListMap) {\n            List<ConnectionModel> processList = connectionModelListMap.get(id);\n            if (processList == null) {\n                processList = new ArrayList<>();\n                connectionModelListMap.put(id, processList);\n            }\n            processList.add(model);\n        }\n    }\n\n    @Override\n    public void updateConnectionModel(int id, int index, long currentOffset) {\n        synchronized (connectionModelListMap) {\n            final List<ConnectionModel> processList = connectionModelListMap.get(id);\n            if (processList == null) return;\n\n            for (ConnectionModel connectionModel : processList) {\n                if (connectionModel.getIndex() == index) {\n                    connectionModel.setCurrentOffset(currentOffset);\n                    return;\n                }\n            }\n        }\n    }\n\n    @Override\n    public void updateConnectionCount(int id, int count) {\n    }\n\n    @Override\n    public void insert(FileDownloadModel downloadModel) {\n        synchronized (downloaderModelMap) {\n            downloaderModelMap.put(downloadModel.getId(), downloadModel);\n        }\n    }\n\n    @Override\n    public void update(FileDownloadModel downloadModel) {\n        if (downloadModel == null) {\n            FileDownloadLog.w(this, \"update but model == null!\");\n            return;\n        }\n\n        if (find(downloadModel.getId()) != null) {\n            // 替换\n            synchronized (downloaderModelMap) {\n                downloaderModelMap.remove(downloadModel.getId());\n                downloaderModelMap.put(downloadModel.getId(), downloadModel);\n            }\n        } else {\n            insert(downloadModel);\n        }\n    }\n\n    @Override\n    public boolean remove(int id) {\n        synchronized (downloaderModelMap) {\n            downloaderModelMap.remove(id);\n        }\n        return true;\n    }\n\n    @Override\n    public void clear() {\n        synchronized (downloaderModelMap) {\n            downloaderModelMap.clear();\n        }\n    }\n\n    @Override\n    public void updateOldEtagOverdue(int id, String newEtag, long sofar, long total,\n                                     int connectionCount) {\n    }\n\n    @Override\n    public void updateConnected(int id, long total, String etag, String filename) {\n    }\n\n    @Override\n    public void updateProgress(int id, long sofarBytes) {\n    }\n\n    @Override\n    public void updateError(int id, Throwable throwable, long sofar) {\n    }\n\n    @Override\n    public void updateRetry(int id, Throwable throwable) {\n    }\n\n    @Override\n    public void updateCompleted(int id, final long total) {\n        remove(id);\n    }\n\n    @Override\n    public void updatePause(int id, long sofar) {\n    }\n\n    @Override\n    public void updatePending(int id) {\n    }\n\n    @Override\n    public FileDownloadDatabase.Maintainer maintainer() {\n        return new Maintainer();\n    }\n\n    class Maintainer implements FileDownloadDatabase.Maintainer {\n\n        @Override\n        public Iterator<FileDownloadModel> iterator() {\n            return new MaintainerIterator();\n        }\n\n        @Override\n        public void onFinishMaintain() {\n        }\n\n        @Override\n        public void onRemovedInvalidData(FileDownloadModel model) {\n        }\n\n        @Override\n        public void onRefreshedValidData(FileDownloadModel model) {\n        }\n\n        @Override\n        public void changeFileDownloadModelId(int oldId, FileDownloadModel modelWithNewId) {\n        }\n\n    }\n\n    class MaintainerIterator implements Iterator<FileDownloadModel> {\n\n        MaintainerIterator() {\n        }\n\n        @Override\n        public boolean hasNext() {\n            return false;\n        }\n\n        @Override\n        public FileDownloadModel next() {\n            return null;\n        }\n\n        @Override\n        public void remove() {\n        }\n    }\n\n    public static class Maker implements FileDownloadHelper.DatabaseCustomMaker {\n\n        @Override\n        public FileDownloadDatabase customMake() {\n            return new NoDatabaseImpl();\n        }\n    }\n}\n"
  },
  {
    "path": "library/src/main/java/com/liulishuo/filedownloader/database/RemitDatabase.java",
    "content": "/*\n * Copyright (c) 2015 LingoChamp Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *   http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.liulishuo.filedownloader.database;\n\nimport android.os.Handler;\nimport android.os.HandlerThread;\nimport android.os.Message;\n\nimport com.liulishuo.filedownloader.model.ConnectionModel;\nimport com.liulishuo.filedownloader.model.FileDownloadModel;\nimport com.liulishuo.filedownloader.util.FileDownloadHelper;\nimport com.liulishuo.filedownloader.util.FileDownloadLog;\nimport com.liulishuo.filedownloader.util.FileDownloadProperties;\nimport com.liulishuo.filedownloader.util.FileDownloadUtils;\n\nimport java.util.ArrayList;\nimport java.util.List;\nimport java.util.concurrent.atomic.AtomicInteger;\nimport java.util.concurrent.locks.LockSupport;\n\n/**\n * If one data insert/update and remove within 2 sec, which will do not effect on\n * {@code realDatabase}.\n */\npublic class RemitDatabase implements FileDownloadDatabase {\n\n    private final NoDatabaseImpl cachedDatabase;\n    private final SqliteDatabaseImpl realDatabase;\n\n\n    private Handler handler;\n    private final long minInterval;\n\n    private final List<Integer> freeToDBIdList = new ArrayList<>();\n    private AtomicInteger handlingId = new AtomicInteger();\n    private volatile Thread parkThread;\n\n    private static final int WHAT_CLEAN_LOCK = 0;\n\n    public RemitDatabase() {\n        this.cachedDatabase = new NoDatabaseImpl();\n        this.realDatabase = new SqliteDatabaseImpl();\n        this.minInterval = FileDownloadProperties.getImpl().downloadMinProgressTime;\n\n        final HandlerThread thread = new HandlerThread(\n                FileDownloadUtils.getThreadPoolName(\"RemitHandoverToDB\"));\n        thread.start();\n        handler = new Handler(thread.getLooper(), new Handler.Callback() {\n            @Override public boolean handleMessage(Message msg) {\n                final int id = msg.what;\n                if (id == WHAT_CLEAN_LOCK) {\n                    if (parkThread != null) {\n                        LockSupport.unpark(parkThread);\n                        parkThread = null;\n                    }\n                    return false;\n                }\n\n                try {\n                    handlingId.set(id);\n\n                    syncCacheToDB(id);\n                    freeToDBIdList.add(id);\n                } finally {\n                    handlingId.set(0);\n                    if (parkThread != null) {\n                        LockSupport.unpark(parkThread);\n                        parkThread = null;\n                    }\n                }\n\n                return false;\n            }\n        });\n    }\n\n    private void syncCacheToDB(int id) {\n        if (FileDownloadLog.NEED_LOG) {\n            FileDownloadLog.d(this, \"sync cache to db %d\", id);\n        }\n\n        // need to attention that the `sofar` in `FileDownloadModel` database will\n        // be updated even through this is a multiple connections task. But the\n        // multi-connections task only concern about the `ConnectionModel` database,\n        // so it doesn't matter in current.\n        realDatabase.update(cachedDatabase.find(id));\n        final List<ConnectionModel> modelList = cachedDatabase.findConnectionModel(id);\n        realDatabase.removeConnections(id);\n        for (ConnectionModel connectionModel : modelList) {\n            realDatabase.insertConnectionModel(connectionModel);\n        }\n    }\n\n    private boolean isNoNeedUpdateToRealDB(int id) {\n        return !freeToDBIdList.contains(id);\n    }\n\n    @Override public void onTaskStart(int id) {\n        handler.sendEmptyMessageDelayed(id, minInterval);\n    }\n\n    @Override public FileDownloadModel find(int id) {\n        return this.cachedDatabase.find(id);\n    }\n\n    @Override public List<ConnectionModel> findConnectionModel(int id) {\n        return this.cachedDatabase.findConnectionModel(id);\n    }\n\n    @Override public void removeConnections(int id) {\n        this.cachedDatabase.removeConnections(id);\n        if (isNoNeedUpdateToRealDB(id)) return;\n        this.realDatabase.removeConnections(id);\n\n    }\n\n    @Override public void insertConnectionModel(ConnectionModel model) {\n        this.cachedDatabase.insertConnectionModel(model);\n        final int id = model.getId();\n        if (isNoNeedUpdateToRealDB(id)) return;\n        this.realDatabase.insertConnectionModel(model);\n    }\n\n    @Override public void updateConnectionModel(int id, int index, long currentOffset) {\n        this.cachedDatabase.updateConnectionModel(id, index, currentOffset);\n\n        if (isNoNeedUpdateToRealDB(id)) return;\n        this.realDatabase.updateConnectionModel(id, index, currentOffset);\n    }\n\n    @Override public void updateProgress(int id, long sofarBytes) {\n        this.cachedDatabase.updateProgress(id, sofarBytes);\n        if (isNoNeedUpdateToRealDB(id)) return;\n        this.realDatabase.updateProgress(id, sofarBytes);\n    }\n\n    @Override public void updateConnectionCount(int id, int count) {\n        this.cachedDatabase.updateConnectionCount(id, count);\n        if (isNoNeedUpdateToRealDB(id)) return;\n        this.realDatabase.updateConnectionCount(id, count);\n\n    }\n\n    @Override public void insert(FileDownloadModel downloadModel) {\n        this.cachedDatabase.insert(downloadModel);\n        if (isNoNeedUpdateToRealDB(downloadModel.getId())) return;\n        this.realDatabase.insert(downloadModel);\n    }\n\n    @Override public void update(FileDownloadModel downloadModel) {\n        this.cachedDatabase.update(downloadModel);\n        if (isNoNeedUpdateToRealDB(downloadModel.getId())) return;\n        this.realDatabase.update(downloadModel);\n    }\n\n    @Override public boolean remove(int id) {\n        this.realDatabase.remove(id);\n        return this.cachedDatabase.remove(id);\n    }\n\n    @Override public void clear() {\n        this.cachedDatabase.clear();\n        this.realDatabase.clear();\n    }\n\n    @Override public void updateOldEtagOverdue(int id, String newEtag, long sofar, long total,\n                                               int connectionCount) {\n        this.cachedDatabase.updateOldEtagOverdue(id, newEtag, sofar, total, connectionCount);\n        if (isNoNeedUpdateToRealDB(id)) return;\n        this.realDatabase.updateOldEtagOverdue(id, newEtag, sofar, total, connectionCount);\n    }\n\n    @Override public void updateConnected(int id, long total, String etag, String filename) {\n        this.cachedDatabase.updateConnected(id, total, etag, filename);\n        if (isNoNeedUpdateToRealDB(id)) return;\n        this.realDatabase.updateConnected(id, total, etag, filename);\n    }\n\n    @Override public void updatePending(int id) {\n        this.cachedDatabase.updatePending(id);\n        if (isNoNeedUpdateToRealDB(id)) return;\n        this.realDatabase.updatePending(id);\n    }\n\n    @Override public void updateRetry(int id, Throwable throwable) {\n        this.cachedDatabase.updateRetry(id, throwable);\n        if (isNoNeedUpdateToRealDB(id)) return;\n        this.realDatabase.updateRetry(id, throwable);\n    }\n\n    private void ensureCacheToDB(int id) {\n        handler.removeMessages(id);\n        if (handlingId.get() == id) {\n            parkThread = Thread.currentThread();\n            handler.sendEmptyMessage(WHAT_CLEAN_LOCK);\n            LockSupport.park();\n        } else {\n            syncCacheToDB(id);\n        }\n    }\n\n    @Override public void updateError(int id, Throwable throwable, long sofar) {\n        this.cachedDatabase.updateError(id, throwable, sofar);\n        if (isNoNeedUpdateToRealDB(id)) {\n            ensureCacheToDB(id);\n        }\n        this.realDatabase.updateError(id, throwable, sofar);\n        freeToDBIdList.remove((Integer) id);\n    }\n\n    @Override public void updateCompleted(int id, long total) {\n        this.cachedDatabase.updateCompleted(id, total);\n        if (isNoNeedUpdateToRealDB(id)) {\n            handler.removeMessages(id);\n            if (handlingId.get() == id) {\n                parkThread = Thread.currentThread();\n                handler.sendEmptyMessage(WHAT_CLEAN_LOCK);\n                LockSupport.park();\n                this.realDatabase.updateCompleted(id, total);\n            }\n        } else {\n            this.realDatabase.updateCompleted(id, total);\n        }\n        freeToDBIdList.remove((Integer) id);\n    }\n\n    @Override public void updatePause(int id, long sofar) {\n        this.cachedDatabase.updatePause(id, sofar);\n        if (isNoNeedUpdateToRealDB(id)) {\n            ensureCacheToDB(id);\n        }\n        this.realDatabase.updatePause(id, sofar);\n        freeToDBIdList.remove((Integer) id);\n    }\n\n\n    @Override public Maintainer maintainer() {\n        return this.realDatabase.maintainer(this.cachedDatabase.downloaderModelMap,\n                this.cachedDatabase.connectionModelListMap);\n    }\n\n    public static class Maker implements FileDownloadHelper.DatabaseCustomMaker {\n\n        @Override public FileDownloadDatabase customMake() {\n            return new RemitDatabase();\n        }\n    }\n}\n"
  },
  {
    "path": "library/src/main/java/com/liulishuo/filedownloader/database/SqliteDatabaseImpl.java",
    "content": "/*\n * Copyright (c) 2015 LingoChamp Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *   http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.liulishuo.filedownloader.database;\n\nimport android.content.ContentValues;\nimport android.database.Cursor;\nimport android.database.sqlite.SQLiteDatabase;\nimport android.text.TextUtils;\nimport android.util.SparseArray;\n\nimport com.liulishuo.filedownloader.model.ConnectionModel;\nimport com.liulishuo.filedownloader.model.FileDownloadModel;\nimport com.liulishuo.filedownloader.model.FileDownloadStatus;\nimport com.liulishuo.filedownloader.util.FileDownloadHelper;\nimport com.liulishuo.filedownloader.util.FileDownloadLog;\nimport com.liulishuo.filedownloader.util.FileDownloadUtils;\n\nimport java.util.ArrayList;\nimport java.util.Iterator;\nimport java.util.List;\n\n/**\n * Persist data to SQLite database.\n *\n * You can valid this database implementation through:\n * <p>\n * class MyApplication extends Application {\n *     ...\n *     public void onCreate() {\n *          ...\n *          FileDownloader.setupOnApplicationOnCreate(this)\n *              .database(SqliteDatabaseImpl.createMaker())\n *              ...\n *              .commit();\n *          ...\n *     }\n *     ...\n * }\n *\n */\npublic class SqliteDatabaseImpl implements FileDownloadDatabase {\n\n    private final SQLiteDatabase db;\n\n    public static final String TABLE_NAME = \"filedownloader\";\n    public static final String CONNECTION_TABLE_NAME = \"filedownloaderConnection\";\n\n    public static Maker createMaker() {\n        return new Maker();\n    }\n\n    public SqliteDatabaseImpl() {\n        SqliteDatabaseOpenHelper openHelper = new SqliteDatabaseOpenHelper(\n                FileDownloadHelper.getAppContext());\n\n        db = openHelper.getWritableDatabase();\n    }\n\n    @Override public void onTaskStart(int id) {\n    }\n\n    @Override\n    public FileDownloadModel find(final int id) {\n        Cursor c = null;\n        try {\n            c = db.rawQuery(FileDownloadUtils.formatString(\"SELECT * FROM %s WHERE %s = ?\",\n                    TABLE_NAME, FileDownloadModel.ID), new String[]{Integer.toString(id)});\n\n            if (c.moveToNext()) return createFromCursor(c);\n\n        } finally {\n            if (c != null) c.close();\n        }\n\n        return null;\n    }\n\n    @Override\n    public List<ConnectionModel> findConnectionModel(int id) {\n        final List<ConnectionModel> resultList = new ArrayList<>();\n\n        Cursor c = null;\n        try {\n            c = db.rawQuery(FileDownloadUtils.formatString(\"SELECT * FROM %s WHERE %s = ?\",\n                    CONNECTION_TABLE_NAME, ConnectionModel.ID), new String[]{Integer.toString(id)});\n\n            while (c.moveToNext()) {\n                final ConnectionModel model = new ConnectionModel();\n                model.setId(id);\n                model.setIndex(c.getInt(c.getColumnIndex(ConnectionModel.INDEX)));\n                model.setStartOffset(c.getLong(c.getColumnIndex(ConnectionModel.START_OFFSET)));\n                model.setCurrentOffset(c.getLong(c.getColumnIndex(ConnectionModel.CURRENT_OFFSET)));\n                model.setEndOffset(c.getLong(c.getColumnIndex(ConnectionModel.END_OFFSET)));\n\n                resultList.add(model);\n            }\n        } finally {\n            if (c != null) c.close();\n        }\n\n        return resultList;\n    }\n\n    @Override\n    public void removeConnections(int id) {\n        db.execSQL(\"DELETE FROM \" + CONNECTION_TABLE_NAME + \" WHERE \"\n                + ConnectionModel.ID + \" = \" + id);\n    }\n\n    @Override\n    public void insertConnectionModel(ConnectionModel model) {\n        db.insert(CONNECTION_TABLE_NAME, null, model.toContentValues());\n    }\n\n    @Override\n    public void updateConnectionModel(int id, int index, long currentOffset) {\n        final ContentValues values = new ContentValues();\n        values.put(ConnectionModel.CURRENT_OFFSET, currentOffset);\n        db.update(CONNECTION_TABLE_NAME, values,\n                ConnectionModel.ID + \" = ? AND \" + ConnectionModel.INDEX + \" = ?\",\n                new String[]{Integer.toString(id), Integer.toString(index)});\n    }\n\n    @Override\n    public void updateConnectionCount(int id, int count) {\n        ContentValues values = new ContentValues();\n        values.put(FileDownloadModel.CONNECTION_COUNT, count);\n        db.update(TABLE_NAME, values,\n                FileDownloadModel.ID + \" = ? \", new String[]{Integer.toString(id)});\n    }\n\n    @Override\n    public void insert(FileDownloadModel downloadModel) {\n        db.insert(TABLE_NAME, null, downloadModel.toContentValues());\n    }\n\n    @Override\n    public void update(FileDownloadModel downloadModel) {\n        if (downloadModel == null) {\n            FileDownloadLog.w(this, \"update but model == null!\");\n            return;\n        }\n\n        if (find(downloadModel.getId()) != null) {\n            // db\n            ContentValues cv = downloadModel.toContentValues();\n            db.update(TABLE_NAME, cv, FileDownloadModel.ID + \" = ? \",\n                    new String[]{String.valueOf(downloadModel.getId())});\n        } else {\n            insert(downloadModel);\n        }\n    }\n\n    @Override\n    public boolean remove(int id) {\n        return db\n                .delete(TABLE_NAME, FileDownloadModel.ID + \" = ?\", new String[]{String.valueOf(id)})\n                != 0;\n    }\n\n    @Override\n    public void clear() {\n        db.delete(TABLE_NAME, null, null);\n        db.delete(CONNECTION_TABLE_NAME, null, null);\n    }\n\n    @Override\n    public void updateOldEtagOverdue(int id, String newEtag, long sofar, long total,\n                                     int connectionCount) {\n        ContentValues values = new ContentValues();\n        values.put(FileDownloadModel.SOFAR, sofar);\n        values.put(FileDownloadModel.TOTAL, total);\n        values.put(FileDownloadModel.ETAG, newEtag);\n        values.put(FileDownloadModel.CONNECTION_COUNT, connectionCount);\n\n        update(id, values);\n\n    }\n\n    @Override\n    public void updateConnected(int id, long total, String etag, String filename) {\n        ContentValues cv = new ContentValues();\n        cv.put(FileDownloadModel.STATUS, FileDownloadStatus.connected);\n        cv.put(FileDownloadModel.TOTAL, total);\n        cv.put(FileDownloadModel.ETAG, etag); // maybe null.\n        cv.put(FileDownloadModel.FILENAME, filename); // maybe null.\n\n        update(id, cv);\n    }\n\n    @Override\n    public void updateProgress(int id, long sofarBytes) {\n        ContentValues cv = new ContentValues();\n        cv.put(FileDownloadModel.STATUS, FileDownloadStatus.progress);\n        cv.put(FileDownloadModel.SOFAR, sofarBytes);\n\n        update(id, cv);\n    }\n\n    @Override\n    public void updateError(int id, Throwable throwable, long sofar) {\n        ContentValues cv = new ContentValues();\n        cv.put(FileDownloadModel.ERR_MSG, throwable.toString());\n        cv.put(FileDownloadModel.STATUS, FileDownloadStatus.error);\n        cv.put(FileDownloadModel.SOFAR, sofar);\n\n        update(id, cv);\n    }\n\n    @Override\n    public void updateRetry(int id, Throwable throwable) {\n        ContentValues cv = new ContentValues();\n        cv.put(FileDownloadModel.ERR_MSG, throwable.toString());\n        cv.put(FileDownloadModel.STATUS, FileDownloadStatus.retry);\n\n        update(id, cv);\n    }\n\n    @Override\n    public void updateCompleted(int id, final long total) {\n        remove(id);\n    }\n\n    @Override\n    public void updatePause(int id, long sofar) {\n        ContentValues cv = new ContentValues();\n        cv.put(FileDownloadModel.STATUS, FileDownloadStatus.paused);\n        cv.put(FileDownloadModel.SOFAR, sofar);\n\n        update(id, cv);\n    }\n\n    @Override\n    public void updatePending(int id) {\n        // No need to persist pending status.\n    }\n\n    @Override\n    public FileDownloadDatabase.Maintainer maintainer() {\n        return new Maintainer();\n    }\n\n    public FileDownloadDatabase.Maintainer maintainer(\n            SparseArray<FileDownloadModel> downloaderModelMap,\n            SparseArray<List<ConnectionModel>> connectionModelListMap) {\n        return new Maintainer(downloaderModelMap, connectionModelListMap);\n    }\n\n    private void update(final int id, final ContentValues cv) {\n        db.update(TABLE_NAME, cv, FileDownloadModel.ID + \" = ? \", new String[]{String.valueOf(id)});\n    }\n\n    public class Maintainer implements FileDownloadDatabase.Maintainer {\n\n        private final SparseArray<FileDownloadModel> needChangeIdList = new SparseArray<>();\n        private MaintainerIterator currentIterator;\n\n        private final SparseArray<FileDownloadModel> downloaderModelMap;\n        private final SparseArray<List<ConnectionModel>> connectionModelListMap;\n\n        Maintainer() {\n            this(null, null);\n        }\n\n        Maintainer(SparseArray<FileDownloadModel> downloaderModelMap,\n                   SparseArray<List<ConnectionModel>> connectionModelListMap) {\n            this.downloaderModelMap = downloaderModelMap;\n            this.connectionModelListMap = connectionModelListMap;\n        }\n\n        @Override\n        public Iterator<FileDownloadModel> iterator() {\n            return currentIterator = new MaintainerIterator();\n        }\n\n        @Override\n        public void onFinishMaintain() {\n            if (currentIterator != null) currentIterator.onFinishMaintain();\n\n            final int length = needChangeIdList.size();\n            if (length < 0) return;\n\n            db.beginTransaction();\n            try {\n                for (int i = 0; i < length; i++) {\n                    final int oldId = needChangeIdList.keyAt(i);\n                    final FileDownloadModel modelWithNewId = needChangeIdList.get(oldId);\n                    db.delete(TABLE_NAME, FileDownloadModel.ID + \" = ?\",\n                            new String[]{String.valueOf(oldId)});\n                    db.insert(TABLE_NAME, null, modelWithNewId.toContentValues());\n\n                    if (modelWithNewId.getConnectionCount() > 1) {\n                        List<ConnectionModel> connectionModelList = findConnectionModel(oldId);\n                        if (connectionModelList.size() <= 0) continue;\n\n                        db.delete(CONNECTION_TABLE_NAME, ConnectionModel.ID + \" = ?\",\n                                new String[]{String.valueOf(oldId)});\n                        for (ConnectionModel connectionModel : connectionModelList) {\n                            connectionModel.setId(modelWithNewId.getId());\n                            db.insert(CONNECTION_TABLE_NAME, null,\n                                    connectionModel.toContentValues());\n                        }\n                    }\n                }\n\n                // initial cache of connection model\n                if (downloaderModelMap != null && connectionModelListMap != null) {\n                    final int size = downloaderModelMap.size();\n                    for (int i = 0; i < size; i++) {\n                        final int id = downloaderModelMap.valueAt(i).getId();\n                        final List<ConnectionModel> connectionModelList = findConnectionModel(id);\n\n                        if (connectionModelList != null && connectionModelList.size() > 0) {\n                            connectionModelListMap.put(id, connectionModelList);\n                        }\n                    }\n                }\n\n                db.setTransactionSuccessful();\n            } finally {\n                db.endTransaction();\n            }\n\n        }\n\n        @Override\n        public void onRemovedInvalidData(FileDownloadModel model) {\n        }\n\n        @Override\n        public void onRefreshedValidData(FileDownloadModel model) {\n            if (downloaderModelMap != null) downloaderModelMap.put(model.getId(), model);\n        }\n\n        @Override\n        public void changeFileDownloadModelId(int oldId, FileDownloadModel modelWithNewId) {\n            needChangeIdList.put(oldId, modelWithNewId);\n        }\n\n    }\n\n    class MaintainerIterator implements Iterator<FileDownloadModel> {\n        private final Cursor c;\n        private final List<Integer> needRemoveId = new ArrayList<>();\n        private int currentId;\n\n\n        MaintainerIterator() {\n            c = db.rawQuery(\"SELECT * FROM \" + TABLE_NAME, null);\n        }\n\n        @Override\n        public boolean hasNext() {\n            return c.moveToNext();\n        }\n\n        @Override\n        public FileDownloadModel next() {\n            final FileDownloadModel model = createFromCursor(c);\n\n            currentId = model.getId();\n\n            return model;\n        }\n\n        @Override\n        public void remove() {\n            needRemoveId.add(currentId);\n        }\n\n        void onFinishMaintain() {\n            c.close();\n\n            if (!needRemoveId.isEmpty()) {\n                String args = TextUtils.join(\", \", needRemoveId);\n                if (FileDownloadLog.NEED_LOG) {\n                    FileDownloadLog.d(this, \"delete %s\", args);\n                }\n                //noinspection ThrowFromFinallyBlock\n                db.execSQL(FileDownloadUtils.formatString(\"DELETE FROM %s WHERE %s IN (%s);\",\n                        TABLE_NAME, FileDownloadModel.ID, args));\n                db.execSQL(FileDownloadUtils.formatString(\"DELETE FROM %s WHERE %s IN (%s);\",\n                        CONNECTION_TABLE_NAME, ConnectionModel.ID, args));\n            }\n        }\n\n    }\n\n    private static FileDownloadModel createFromCursor(Cursor c) {\n        final FileDownloadModel model = new FileDownloadModel();\n        model.setId(c.getInt(c.getColumnIndex(FileDownloadModel.ID)));\n        model.setUrl(c.getString(c.getColumnIndex(FileDownloadModel.URL)));\n        model.setPath(c.getString(c.getColumnIndex(FileDownloadModel.PATH)),\n                c.getShort(c.getColumnIndex(FileDownloadModel.PATH_AS_DIRECTORY)) == 1);\n        model.setStatus((byte) c.getShort(c.getColumnIndex(FileDownloadModel.STATUS)));\n        model.setSoFar(c.getLong(c.getColumnIndex(FileDownloadModel.SOFAR)));\n        model.setTotal(c.getLong(c.getColumnIndex(FileDownloadModel.TOTAL)));\n        model.setErrMsg(c.getString(c.getColumnIndex(FileDownloadModel.ERR_MSG)));\n        model.setETag(c.getString(c.getColumnIndex(FileDownloadModel.ETAG)));\n        model.setFilename(c.getString(c.getColumnIndex(FileDownloadModel.FILENAME)));\n        model.setConnectionCount(\n                c.getInt(c.getColumnIndex(FileDownloadModel.CONNECTION_COUNT)));\n\n        return model;\n    }\n\n    public static class Maker implements FileDownloadHelper.DatabaseCustomMaker {\n\n        @Override\n        public FileDownloadDatabase customMake() {\n            return new SqliteDatabaseImpl();\n        }\n    }\n}\n"
  },
  {
    "path": "library/src/main/java/com/liulishuo/filedownloader/database/SqliteDatabaseOpenHelper.java",
    "content": "/*\n * Copyright (c) 2015 LingoChamp Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *   http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.liulishuo.filedownloader.database;\n\nimport android.content.ContentValues;\nimport android.content.Context;\nimport android.database.sqlite.SQLiteDatabase;\nimport android.database.sqlite.SQLiteOpenHelper;\nimport android.os.Build;\n\nimport com.liulishuo.filedownloader.model.ConnectionModel;\nimport com.liulishuo.filedownloader.model.FileDownloadModel;\n\n\n/**\n * The default opener of the filedownloader database helper.\n */\npublic class SqliteDatabaseOpenHelper extends SQLiteOpenHelper {\n    private static final String DATABASE_NAME = \"filedownloader.db\";\n    private static final int DATABASE_VERSION = 4;\n\n    public SqliteDatabaseOpenHelper(final Context context) {\n        super(context, DATABASE_NAME, null, DATABASE_VERSION);\n    }\n\n    @Override\n    public void onOpen(SQLiteDatabase db) {\n        super.onOpen(db);\n\n        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) {\n            setWriteAheadLoggingEnabled(true);\n        } else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) {\n            db.enableWriteAheadLogging();\n        }\n    }\n\n    @Override\n    public void onCreate(SQLiteDatabase db) {\n        db.execSQL(\"CREATE TABLE IF NOT EXISTS \"\n                + SqliteDatabaseImpl.TABLE_NAME + \"( \"\n                + FileDownloadModel.ID + \" INTEGER PRIMARY KEY, \"  // id\n                + FileDownloadModel.URL + \" VARCHAR, \"  // url\n                + FileDownloadModel.PATH + \" VARCHAR, \"  // path\n                + FileDownloadModel.STATUS + \" TINYINT(7), \"  // status\n                + FileDownloadModel.SOFAR + \" INTEGER, \" // so far bytes\n                + FileDownloadModel.TOTAL + \" INTEGER, \" // total bytes\n                + FileDownloadModel.ERR_MSG + \" VARCHAR, \"  // error message\n                + FileDownloadModel.ETAG + \" VARCHAR, \" // etag\n                + FileDownloadModel.PATH_AS_DIRECTORY + \" TINYINT(1) DEFAULT 0, \"//path as directory\n                + FileDownloadModel.FILENAME + \" VARCHAR, \" // path as directory\n                + FileDownloadModel.CONNECTION_COUNT + \" INTEGER DEFAULT 1\" // connection count\n                + \")\");\n        db.execSQL(\"CREATE TABLE IF NOT EXISTS \"\n                + SqliteDatabaseImpl.CONNECTION_TABLE_NAME + \"( \"\n                + ConnectionModel.ID + \" INTEGER, \"\n                + ConnectionModel.INDEX + \" INTEGER, \"\n                + ConnectionModel.START_OFFSET + \" INTEGER, \"\n                + ConnectionModel.CURRENT_OFFSET + \" INTEGER, \"\n                + ConnectionModel.END_OFFSET + \" INTEGER, \"\n                + \"PRIMARY KEY ( \" + ConnectionModel.ID + \", \" + ConnectionModel.INDEX + \" )\"\n                + \")\");\n    }\n\n    @Override\n    public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {\n\n        if (oldVersion < 2) {\n            String addAsDirectoryColumn = \"ALTER TABLE \" + SqliteDatabaseImpl.TABLE_NAME\n                    + \" ADD COLUMN \" + FileDownloadModel.PATH_AS_DIRECTORY\n                    + \" TINYINT(1) DEFAULT 0\";\n            db.execSQL(addAsDirectoryColumn);\n\n            String addFilenameColumn = \"ALTER TABLE \" + SqliteDatabaseImpl.TABLE_NAME\n                    + \" ADD COLUMN \" + FileDownloadModel.FILENAME\n                    + \" VARCHAR\";\n            db.execSQL(addFilenameColumn);\n        }\n\n        if (oldVersion < 3) {\n            final String addConnectionCount = \"ALTER TABLE \" + SqliteDatabaseImpl.TABLE_NAME\n                    + \" ADD COLUMN \" + FileDownloadModel.CONNECTION_COUNT\n                    + \" INTEGER DEFAULT 1\";\n            db.execSQL(addConnectionCount);\n\n            db.execSQL(\"CREATE TABLE IF NOT EXISTS \"\n                    + SqliteDatabaseImpl.CONNECTION_TABLE_NAME + \"( \"\n                    + ConnectionModel.ID + \" INTEGER, \"\n                    + ConnectionModel.INDEX + \" INTEGER, \"\n                    + ConnectionModel.START_OFFSET + \" INTEGER, \"\n                    + ConnectionModel.CURRENT_OFFSET + \" INTEGER, \"\n                    + ConnectionModel.END_OFFSET + \" INTEGER, \"\n                    + \"PRIMARY KEY ( \" + ConnectionModel.ID + \", \" + ConnectionModel.INDEX  + \" )\"\n                    + \")\");\n        }\n\n        if (oldVersion < 4) {\n            ContentValues values = new ContentValues();\n            values.put(ConnectionModel.END_OFFSET, -1);\n            String whereClause = ConnectionModel.END_OFFSET + \" = ? AND \"\n                    + ConnectionModel.START_OFFSET + \" > ?\";\n            db.update(SqliteDatabaseImpl.CONNECTION_TABLE_NAME, values,\n                    whereClause, new String[]{\"0\", \"0\"});\n        }\n    }\n\n    @Override public void onDowngrade(SQLiteDatabase db, int oldVersion, int newVersion) {\n        db.delete(SqliteDatabaseImpl.TABLE_NAME, null, null);\n        db.delete(SqliteDatabaseImpl.CONNECTION_TABLE_NAME, null, null);\n    }\n}\n"
  },
  {
    "path": "library/src/main/java/com/liulishuo/filedownloader/download/ConnectTask.java",
    "content": "/*\n * Copyright (c) 2015 LingoChamp Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *   http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.liulishuo.filedownloader.download;\n\nimport android.text.TextUtils;\n\nimport com.liulishuo.filedownloader.connection.FileDownloadConnection;\nimport com.liulishuo.filedownloader.connection.RedirectHandler;\nimport com.liulishuo.filedownloader.model.FileDownloadHeader;\nimport com.liulishuo.filedownloader.util.FileDownloadLog;\nimport com.liulishuo.filedownloader.util.FileDownloadUtils;\n\nimport java.io.IOException;\nimport java.net.ProtocolException;\nimport java.util.ArrayList;\nimport java.util.HashMap;\nimport java.util.List;\nimport java.util.Map;\nimport java.util.Set;\n\n/**\n * The connect task which used for connect to the backend.\n */\npublic class ConnectTask {\n\n    final int downloadId;\n    final String url;\n    final FileDownloadHeader header;\n\n    private ConnectionProfile profile;\n    private String etag;\n\n    private Map<String, List<String>> requestHeader;\n    private List<String> redirectedUrlList;\n\n\n    private ConnectTask(ConnectionProfile profile,\n                        int downloadId, String url, String etag, FileDownloadHeader header) {\n        this.downloadId = downloadId;\n        this.url = url;\n        this.etag = etag;\n        this.header = header;\n        this.profile = profile;\n    }\n\n    void updateConnectionProfile(long downloadedOffset) {\n        if (downloadedOffset == profile.currentOffset) {\n            FileDownloadLog.w(this, \"no data download, no need to update\");\n            return;\n        }\n        final long newContentLength =\n                profile.contentLength - (downloadedOffset - profile.currentOffset);\n        profile = ConnectionProfile.ConnectionProfileBuild.buildConnectionProfile(\n                profile.startOffset,\n                downloadedOffset,\n                profile.endOffset,\n                newContentLength);\n        if (FileDownloadLog.NEED_LOG) {\n            FileDownloadLog.i(this, \"after update profile:%s\", profile);\n        }\n    }\n\n    FileDownloadConnection connect() throws IOException, IllegalAccessException {\n        FileDownloadConnection connection = CustomComponentHolder.getImpl().createConnection(url);\n\n        addUserRequiredHeader(connection);\n        addRangeHeader(connection);\n        fixNeededHeader(connection);\n\n        // init request\n        // get the request header in here, because of there are many connection\n        // component(such as HttpsURLConnectionImpl, HttpURLConnectionImpl in okhttp3) don't\n        // allow access to the request header after it connected.\n        requestHeader = connection.getRequestHeaderFields();\n        if (FileDownloadLog.NEED_LOG) {\n            FileDownloadLog.d(this, \"<---- %s request header %s\", downloadId, requestHeader);\n        }\n\n        connection.execute();\n        redirectedUrlList = new ArrayList<>();\n        connection = RedirectHandler.process(requestHeader, connection, redirectedUrlList);\n\n        if (FileDownloadLog.NEED_LOG) {\n            FileDownloadLog.d(this, \"----> %s response header %s\", downloadId,\n                    connection.getResponseHeaderFields());\n        }\n\n        return connection;\n    }\n\n    private void addUserRequiredHeader(FileDownloadConnection connection) {\n        final HashMap<String, List<String>> additionHeaders;\n        if (header != null) {\n            additionHeaders = header.getHeaders();\n\n            if (additionHeaders != null) {\n                if (FileDownloadLog.NEED_LOG) {\n                    FileDownloadLog.v(this, \"%d add outside header: %s\",\n                            downloadId, additionHeaders);\n                }\n\n                String name;\n                List<String> list;\n\n                // add addition headers which is provided by the user\n                Set<Map.Entry<String, List<String>>> entries = additionHeaders.entrySet();\n                for (Map.Entry<String, List<String>> e : entries) {\n                    name = e.getKey();\n                    list = e.getValue();\n                    if (list != null) {\n                        for (String value : list) {\n                            connection.addHeader(name, value);\n                        }\n                    }\n                }\n\n            }\n        }\n    }\n\n    private void addRangeHeader(FileDownloadConnection connection) throws ProtocolException {\n        if (connection.dispatchAddResumeOffset(etag, profile.startOffset)) {\n            return;\n        }\n\n        if (!TextUtils.isEmpty(etag)) {\n            connection.addHeader(\"If-Match\", etag);\n        }\n        profile.processProfile(connection);\n    }\n\n    private void fixNeededHeader(FileDownloadConnection connection) {\n        if (header == null || header.getHeaders().get(\"User-Agent\") == null) {\n            connection.addHeader(\"User-Agent\", FileDownloadUtils.defaultUserAgent());\n        }\n    }\n\n    boolean isRangeNotFromBeginning() {\n        return profile.currentOffset > 0;\n    }\n\n    String getFinalRedirectedUrl() {\n        if (redirectedUrlList != null && !redirectedUrlList.isEmpty()) {\n            return redirectedUrlList.get(redirectedUrlList.size() - 1);\n        }\n\n        return null;\n    }\n\n    public Map<String, List<String>> getRequestHeader() {\n        return requestHeader;\n    }\n\n    public ConnectionProfile getProfile() {\n        return profile;\n    }\n\n    public void retryOnConnectedWithNewParam(ConnectionProfile profile, String etag)\n            throws Reconnect {\n        if (profile == null) throw new IllegalArgumentException();\n        this.profile = profile;\n        this.etag = etag;\n        throw new Reconnect();\n    }\n\n    class Reconnect extends Throwable {\n    }\n\n// -----------------\n\n    static class Builder {\n\n        private Integer downloadId;\n        private String url;\n        private String etag;\n        private FileDownloadHeader header;\n        private ConnectionProfile connectionProfile;\n\n        public Builder setDownloadId(int downloadId) {\n            this.downloadId = downloadId;\n            return this;\n        }\n\n        public Builder setUrl(String url) {\n            this.url = url;\n            return this;\n        }\n\n        public Builder setEtag(String etag) {\n            this.etag = etag;\n            return this;\n        }\n\n        public Builder setHeader(FileDownloadHeader header) {\n            this.header = header;\n            return this;\n        }\n\n        public Builder setConnectionProfile(ConnectionProfile model) {\n            this.connectionProfile = model;\n            return this;\n        }\n\n        ConnectTask build() {\n            if (downloadId == null || connectionProfile == null || url == null) {\n                throw new IllegalArgumentException();\n            }\n\n            return new ConnectTask(connectionProfile, downloadId, url, etag, header);\n        }\n    }\n}\n"
  },
  {
    "path": "library/src/main/java/com/liulishuo/filedownloader/download/ConnectionProfile.java",
    "content": "/*\n * Copyright (c) 2015 LingoChamp Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *   http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.liulishuo.filedownloader.download;\n\nimport com.liulishuo.filedownloader.connection.FileDownloadConnection;\nimport com.liulishuo.filedownloader.util.FileDownloadProperties;\nimport com.liulishuo.filedownloader.util.FileDownloadUtils;\n\nimport java.net.ProtocolException;\n\n/**\n * The connection profile for {@link ConnectTask}.\n */\npublic class ConnectionProfile {\n\n    static final int RANGE_INFINITE = -1;\n\n    final long startOffset;\n    final long currentOffset;\n    final long endOffset;\n    final long contentLength;\n\n    private final boolean isForceNoRange;\n\n    private final boolean isTrialConnect;\n\n    /**\n     * This construct is just for build trial connection profile.\n     */\n    private ConnectionProfile() {\n        this.startOffset = 0;\n        this.currentOffset = 0;\n        this.endOffset = 0;\n        this.contentLength = 0;\n\n        this.isForceNoRange = false;\n        this.isTrialConnect = true;\n    }\n\n    private ConnectionProfile(long startOffset, long currentOffset, long endOffset,\n                              long contentLength) {\n        this(startOffset, currentOffset, endOffset, contentLength, false);\n    }\n\n    private ConnectionProfile(long startOffset, long currentOffset, long endOffset,\n                              long contentLength,\n                              boolean isForceNoRange) {\n        if ((startOffset != 0 || endOffset != 0) && isForceNoRange) {\n            throw new IllegalArgumentException();\n        }\n\n        this.startOffset = startOffset;\n        this.currentOffset = currentOffset;\n        this.endOffset = endOffset;\n        this.contentLength = contentLength;\n        this.isForceNoRange = isForceNoRange;\n        this.isTrialConnect = false;\n    }\n\n    public void processProfile(FileDownloadConnection connection) throws ProtocolException {\n        if (isForceNoRange) return;\n\n        if (isTrialConnect && FileDownloadProperties.getImpl().trialConnectionHeadMethod) {\n            connection.setRequestMethod(\"HEAD\");\n        }\n\n        final String range;\n        if (endOffset == RANGE_INFINITE) {\n            range = FileDownloadUtils.formatString(\"bytes=%d-\", currentOffset);\n        } else {\n            range = FileDownloadUtils\n                    .formatString(\"bytes=%d-%d\", currentOffset, endOffset);\n        }\n        connection.addHeader(\"Range\", range);\n    }\n\n    @Override\n    public String toString() {\n        return FileDownloadUtils.formatString(\"range[%d, %d) current offset[%d]\",\n                startOffset, endOffset, currentOffset);\n    }\n\n    public static class ConnectionProfileBuild {\n        public static ConnectionProfile buildTrialConnectionProfile() {\n            return new ConnectionProfile();\n        }\n\n        public static ConnectionProfile buildTrialConnectionProfileNoRange() {\n            return new ConnectionProfile(0, 0, 0, 0, true);\n        }\n\n        public static ConnectionProfile buildBeginToEndConnectionProfile(long contentLength) {\n            return new ConnectionProfile(0, 0, RANGE_INFINITE, contentLength);\n        }\n\n        public static ConnectionProfile buildToEndConnectionProfile(long startOffset,\n                                                                    long currentOffset,\n                                                                    long contentLength) {\n            return new ConnectionProfile(startOffset, currentOffset, RANGE_INFINITE, contentLength);\n        }\n\n        public static ConnectionProfile buildConnectionProfile(long startOffset,\n                                                               long currentOffset,\n                                                               long endOffset,\n                                                               long contentLength) {\n            return new ConnectionProfile(startOffset, currentOffset, endOffset, contentLength);\n        }\n    }\n}\n"
  },
  {
    "path": "library/src/main/java/com/liulishuo/filedownloader/download/CustomComponentHolder.java",
    "content": "/*\n * Copyright (c) 2015 LingoChamp Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *   http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.liulishuo.filedownloader.download;\n\nimport com.liulishuo.filedownloader.connection.FileDownloadConnection;\nimport com.liulishuo.filedownloader.database.FileDownloadDatabase;\nimport com.liulishuo.filedownloader.model.FileDownloadModel;\nimport com.liulishuo.filedownloader.model.FileDownloadStatus;\nimport com.liulishuo.filedownloader.services.DownloadMgrInitialParams;\nimport com.liulishuo.filedownloader.services.ForegroundServiceConfig;\nimport com.liulishuo.filedownloader.stream.FileDownloadOutputStream;\nimport com.liulishuo.filedownloader.util.FileDownloadHelper;\nimport com.liulishuo.filedownloader.util.FileDownloadLog;\nimport com.liulishuo.filedownloader.util.FileDownloadUtils;\n\nimport java.io.File;\nimport java.io.IOException;\nimport java.util.Iterator;\n\n/**\n * The holder for supported custom components.\n */\npublic class CustomComponentHolder {\n    private DownloadMgrInitialParams initialParams;\n\n    private FileDownloadHelper.ConnectionCountAdapter connectionCountAdapter;\n    private FileDownloadHelper.ConnectionCreator connectionCreator;\n    private FileDownloadHelper.OutputStreamCreator outputStreamCreator;\n    private FileDownloadDatabase database;\n    private FileDownloadHelper.IdGenerator idGenerator;\n    private ForegroundServiceConfig foregroundServiceConfig;\n\n    private static final class LazyLoader {\n        private static final CustomComponentHolder INSTANCE = new CustomComponentHolder();\n    }\n\n    public static CustomComponentHolder getImpl() {\n        return LazyLoader.INSTANCE;\n    }\n\n    public void setInitCustomMaker(DownloadMgrInitialParams.InitCustomMaker initCustomMaker) {\n        synchronized (this) {\n            initialParams = new DownloadMgrInitialParams(initCustomMaker);\n            connectionCreator = null;\n            outputStreamCreator = null;\n            database = null;\n            idGenerator = null;\n        }\n    }\n\n    public FileDownloadConnection createConnection(String url) throws IOException {\n        return getConnectionCreator().create(url);\n    }\n\n    public FileDownloadOutputStream createOutputStream(File file) throws IOException {\n        return getOutputStreamCreator().create(file);\n    }\n\n    public FileDownloadHelper.IdGenerator getIdGeneratorInstance() {\n        if (idGenerator != null) return idGenerator;\n\n        synchronized (this) {\n            if (idGenerator == null) {\n                idGenerator = getDownloadMgrInitialParams().createIdGenerator();\n            }\n        }\n\n        return idGenerator;\n    }\n\n    public FileDownloadDatabase getDatabaseInstance() {\n        if (database != null) return database;\n\n        synchronized (this) {\n            if (database == null) {\n                database = getDownloadMgrInitialParams().createDatabase();\n                maintainDatabase(database.maintainer());\n            }\n        }\n\n        return database;\n    }\n\n    public ForegroundServiceConfig getForegroundConfigInstance() {\n        if (foregroundServiceConfig != null) return foregroundServiceConfig;\n\n        synchronized (this) {\n            if (foregroundServiceConfig == null) {\n                foregroundServiceConfig = getDownloadMgrInitialParams()\n                        .createForegroundServiceConfig();\n            }\n        }\n\n        return foregroundServiceConfig;\n    }\n\n    public int getMaxNetworkThreadCount() {\n        return getDownloadMgrInitialParams().getMaxNetworkThreadCount();\n    }\n\n    public boolean isSupportSeek() {\n        return getOutputStreamCreator().supportSeek();\n    }\n\n    public int determineConnectionCount(int downloadId, String url, String path, long totalLength) {\n        return getConnectionCountAdapter()\n                .determineConnectionCount(downloadId, url, path, totalLength);\n    }\n\n    private FileDownloadHelper.ConnectionCountAdapter getConnectionCountAdapter() {\n        if (connectionCountAdapter != null) return connectionCountAdapter;\n\n        synchronized (this) {\n            if (connectionCountAdapter == null) {\n                connectionCountAdapter = getDownloadMgrInitialParams()\n                        .createConnectionCountAdapter();\n            }\n        }\n\n        return connectionCountAdapter;\n    }\n\n    private FileDownloadHelper.ConnectionCreator getConnectionCreator() {\n        if (connectionCreator != null) return connectionCreator;\n\n        synchronized (this) {\n            if (connectionCreator == null) {\n                connectionCreator = getDownloadMgrInitialParams().createConnectionCreator();\n            }\n        }\n\n        return connectionCreator;\n    }\n\n    private FileDownloadHelper.OutputStreamCreator getOutputStreamCreator() {\n        if (outputStreamCreator != null) return outputStreamCreator;\n\n        synchronized (this) {\n            if (outputStreamCreator == null) {\n                outputStreamCreator = getDownloadMgrInitialParams().createOutputStreamCreator();\n            }\n        }\n\n        return outputStreamCreator;\n    }\n\n    private DownloadMgrInitialParams getDownloadMgrInitialParams() {\n        if (initialParams != null) return initialParams;\n\n        synchronized (this) {\n            if (initialParams == null) initialParams = new DownloadMgrInitialParams();\n        }\n\n        return initialParams;\n    }\n\n    private static void maintainDatabase(FileDownloadDatabase.Maintainer maintainer) {\n        final Iterator<FileDownloadModel> iterator = maintainer.iterator();\n        long refreshDataCount = 0;\n        long removedDataCount = 0;\n        long resetIdCount = 0;\n        final FileDownloadHelper.IdGenerator idGenerator = getImpl().getIdGeneratorInstance();\n\n        final long startTimestamp = System.currentTimeMillis();\n        try {\n            while (iterator.hasNext()) {\n                boolean isInvalid = false;\n                final FileDownloadModel model = iterator.next();\n                do {\n                    if (model.getStatus() == FileDownloadStatus.progress\n                            || model.getStatus() == FileDownloadStatus.connected\n                            || model.getStatus() == FileDownloadStatus.error\n                            || (model.getStatus() == FileDownloadStatus.pending && model\n                            .getSoFar() > 0)\n                            ) {\n                        // Ensure can be covered by RESUME FROM BREAKPOINT.\n                        model.setStatus(FileDownloadStatus.paused);\n                    }\n                    final String targetFilePath = model.getTargetFilePath();\n                    if (targetFilePath == null) {\n                        // no target file path, can't used to resume from breakpoint.\n                        isInvalid = true;\n                        break;\n                    }\n\n                    final File targetFile = new File(targetFilePath);\n                    // consider check in new thread, but SQLite lock | file lock aways effect, so\n                    // sync\n                    if (model.getStatus() == FileDownloadStatus.paused\n                            && FileDownloadUtils.isBreakpointAvailable(model.getId(), model,\n                            model.getPath(), null)) {\n                        // can be reused in the old mechanism(no-temp-file).\n\n                        final File tempFile = new File(model.getTempFilePath());\n\n                        if (!tempFile.exists() && targetFile.exists()) {\n                            final boolean successRename = targetFile.renameTo(tempFile);\n                            if (FileDownloadLog.NEED_LOG) {\n                                FileDownloadLog.d(FileDownloadDatabase.class,\n                                        \"resume from the old no-temp-file architecture \"\n                                                + \"[%B], [%s]->[%s]\",\n                                        successRename, targetFile.getPath(), tempFile.getPath());\n\n                            }\n                        }\n                    }\n\n                    /**\n                     * Remove {@code model} from DB if it can't used for judging whether the\n                     * old-downloaded file is valid for reused & it can't used for resuming from\n                     * BREAKPOINT, In other words, {@code model} is no use anymore for\n                     * FileDownloader.\n                     */\n                    if (model.getStatus() == FileDownloadStatus.pending && model.getSoFar() <= 0) {\n                        // This model is redundant.\n                        isInvalid = true;\n                        break;\n                    }\n\n                    if (!FileDownloadUtils.isBreakpointAvailable(model.getId(), model)) {\n                        // It can't used to resuming from breakpoint.\n                        isInvalid = true;\n                        break;\n                    }\n\n                    if (targetFile.exists()) {\n                        // It has already completed downloading.\n                        isInvalid = true;\n                        break;\n                    }\n\n                } while (false);\n\n\n                if (isInvalid) {\n                    iterator.remove();\n                    maintainer.onRemovedInvalidData(model);\n                    removedDataCount++;\n                } else {\n                    final int oldId = model.getId();\n                    final int newId = idGenerator.transOldId(oldId, model.getUrl(), model.getPath(),\n                            model.isPathAsDirectory());\n                    if (newId != oldId) {\n                        if (FileDownloadLog.NEED_LOG) {\n                            FileDownloadLog.d(FileDownloadDatabase.class,\n                                    \"the id is changed on restoring from db:\"\n                                            + \" old[%d] -> new[%d]\",\n                                    oldId, newId);\n                        }\n                        model.setId(newId);\n                        maintainer.changeFileDownloadModelId(oldId, model);\n                        resetIdCount++;\n                    }\n\n                    maintainer.onRefreshedValidData(model);\n                    refreshDataCount++;\n                }\n            }\n\n        } finally {\n            FileDownloadUtils.markConverted(FileDownloadHelper.getAppContext());\n            maintainer.onFinishMaintain();\n            // 566 data consumes about 140ms\n            if (FileDownloadLog.NEED_LOG) {\n                FileDownloadLog.d(FileDownloadDatabase.class,\n                        \"refreshed data count: %d , delete data count: %d, reset id count:\"\n                                + \" %d. consume %d\",\n                        refreshDataCount, removedDataCount, resetIdCount,\n                        System.currentTimeMillis() - startTimestamp);\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "library/src/main/java/com/liulishuo/filedownloader/download/DownloadLaunchRunnable.java",
    "content": "/*\n * Copyright (c) 2015 LingoChamp Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *   http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.liulishuo.filedownloader.download;\n\nimport android.Manifest;\nimport android.os.Process;\n\nimport com.liulishuo.filedownloader.DownloadTask;\nimport com.liulishuo.filedownloader.IThreadPoolMonitor;\nimport com.liulishuo.filedownloader.connection.FileDownloadConnection;\nimport com.liulishuo.filedownloader.database.FileDownloadDatabase;\nimport com.liulishuo.filedownloader.exception.FileDownloadGiveUpRetryException;\nimport com.liulishuo.filedownloader.exception.FileDownloadHttpException;\nimport com.liulishuo.filedownloader.exception.FileDownloadNetworkPolicyException;\nimport com.liulishuo.filedownloader.exception.FileDownloadOutOfSpaceException;\nimport com.liulishuo.filedownloader.exception.FileDownloadSecurityException;\nimport com.liulishuo.filedownloader.model.ConnectionModel;\nimport com.liulishuo.filedownloader.model.FileDownloadHeader;\nimport com.liulishuo.filedownloader.model.FileDownloadModel;\nimport com.liulishuo.filedownloader.model.FileDownloadStatus;\nimport com.liulishuo.filedownloader.stream.FileDownloadOutputStream;\nimport com.liulishuo.filedownloader.util.FileDownloadExecutors;\nimport com.liulishuo.filedownloader.util.FileDownloadHelper;\nimport com.liulishuo.filedownloader.util.FileDownloadLog;\nimport com.liulishuo.filedownloader.util.FileDownloadProperties;\nimport com.liulishuo.filedownloader.util.FileDownloadUtils;\n\nimport java.io.File;\nimport java.io.IOException;\nimport java.net.HttpURLConnection;\nimport java.util.ArrayList;\nimport java.util.List;\nimport java.util.Map;\nimport java.util.concurrent.Callable;\nimport java.util.concurrent.Executors;\nimport java.util.concurrent.Future;\nimport java.util.concurrent.ThreadPoolExecutor;\nimport java.util.concurrent.atomic.AtomicBoolean;\n\n/**\n * You can use this to launch downloading, on here the download will be launched separate following\n * steps:\n * <p/>\n * step 1. create the trial connection\n *          ( this trial connection is used for:\n *                  1. checkup the saved etag is overdue\n *                  2. checkup whether the partial-accept is supported\n *                  3. checkup whether the current connection is chunked. )\n *\n * step 2. if the saved etag is overdue -> jump to step 1 to checkup whether the partial-accept is\n * supported.\n * step 3. if (NOT chunked) & partial-accept & output stream support-seek:\n *              create multiple {@link DownloadTask} to download.\n *         else:\n *              create single first connection and use {@link FetchDataTask} to fetch data from the\n *              connection.\n * <p/>\n * We use {@link DownloadStatusCallback} to handle all events sync to DB/filesystem and callback to\n * user.\n */\npublic class DownloadLaunchRunnable implements Runnable, ProcessCallback {\n\n    private final DownloadStatusCallback statusCallback;\n    private final int defaultConnectionCount = 5;\n    private final FileDownloadModel model;\n    private final FileDownloadHeader userRequestHeader;\n    private final boolean isForceReDownload;\n    private final boolean isWifiRequired;\n\n    private final FileDownloadDatabase database;\n    private final IThreadPoolMonitor threadPoolMonitor;\n\n    private boolean isTriedFixRangeNotSatisfiable;\n\n    int validRetryTimes;\n\n    /**\n     * None of the ranges in the request's Range header field overlap the current extent of the\n     * selected resource or that the set of ranges requested has been rejected due to invalid\n     * ranges or an excessive request of small or overlapping ranges.\n     */\n    private static final int HTTP_REQUESTED_RANGE_NOT_SATISFIABLE = 416;\n    private static final int TOTAL_VALUE_IN_CHUNKED_RESOURCE = -1;\n\n    private boolean isNeedForceDiscardRange = false;\n\n    private final boolean supportSeek;\n\n    private final ArrayList<DownloadRunnable> downloadRunnableList = new ArrayList<>(\n            defaultConnectionCount);\n    private DownloadRunnable singleDownloadRunnable;\n    private boolean isSingleConnection;\n\n    private static final ThreadPoolExecutor DOWNLOAD_EXECUTOR = FileDownloadExecutors\n            .newFixedThreadPool(\"ConnectionBlock\");\n\n    private boolean isResumeAvailableOnDB;\n    private boolean acceptPartial;\n    private boolean isChunked;\n\n    private final AtomicBoolean alive;\n    private volatile boolean paused;\n    private volatile boolean error;\n    private volatile Exception errorException;\n\n    private String redirectedUrl;\n\n    private DownloadLaunchRunnable(FileDownloadModel model, FileDownloadHeader header,\n                                   IThreadPoolMonitor threadPoolMonitor,\n                                   final int minIntervalMillis, int callbackProgressMaxCount,\n                                   boolean isForceReDownload, boolean isWifiRequired,\n                                   int maxRetryTimes) {\n        this.alive = new AtomicBoolean(true);\n        this.paused = false;\n        this.isTriedFixRangeNotSatisfiable = false;\n\n        this.model = model;\n        this.userRequestHeader = header;\n        this.isForceReDownload = isForceReDownload;\n        this.isWifiRequired = isWifiRequired;\n        this.database = CustomComponentHolder.getImpl().getDatabaseInstance();\n        this.supportSeek = CustomComponentHolder.getImpl().isSupportSeek();\n        this.threadPoolMonitor = threadPoolMonitor;\n        this.validRetryTimes = maxRetryTimes;\n\n        this.statusCallback = new DownloadStatusCallback(model,\n                maxRetryTimes, minIntervalMillis, callbackProgressMaxCount);\n    }\n\n    private DownloadLaunchRunnable(DownloadStatusCallback callback, FileDownloadModel model,\n                                   FileDownloadHeader header,\n                                   IThreadPoolMonitor threadPoolMonitor,\n                                   final int minIntervalMillis, int callbackProgressMaxCount,\n                                   boolean isForceReDownload, boolean isWifiRequired,\n                                   int maxRetryTimes) {\n        this.alive = new AtomicBoolean(true);\n        this.paused = false;\n        this.isTriedFixRangeNotSatisfiable = false;\n\n        this.model = model;\n        this.userRequestHeader = header;\n        this.isForceReDownload = isForceReDownload;\n        this.isWifiRequired = isWifiRequired;\n        this.database = CustomComponentHolder.getImpl().getDatabaseInstance();\n        this.supportSeek = CustomComponentHolder.getImpl().isSupportSeek();\n        this.threadPoolMonitor = threadPoolMonitor;\n        this.validRetryTimes = maxRetryTimes;\n\n        this.statusCallback = callback;\n    }\n\n    static DownloadLaunchRunnable createForTest(DownloadStatusCallback callback,\n                                                FileDownloadModel model, FileDownloadHeader header,\n                                                IThreadPoolMonitor threadPoolMonitor,\n                                                final int minIntervalMillis,\n                                                int callbackProgressMaxCount,\n                                                boolean isForceReDownload, boolean isWifiRequired,\n                                                int maxRetryTimes) {\n        return new DownloadLaunchRunnable(callback, model, header, threadPoolMonitor,\n                minIntervalMillis, callbackProgressMaxCount, isForceReDownload, isWifiRequired,\n                maxRetryTimes);\n    }\n\n    public void pause() {\n        this.paused = true;\n\n        if (singleDownloadRunnable != null) singleDownloadRunnable.pause();\n        @SuppressWarnings(\"unchecked\") ArrayList<DownloadRunnable> pauseList =\n                (ArrayList<DownloadRunnable>) downloadRunnableList.clone();\n        for (DownloadRunnable runnable : pauseList) {\n            if (runnable != null) {\n                runnable.pause();\n                // if runnable is null, then that one must be completed and removed\n            }\n        }\n    }\n\n    public void pending() {\n        final List<ConnectionModel> connectionOnDBList = database\n                .findConnectionModel(model.getId());\n        //inspect model can be resumed or not, if false, the previous sofar cannot be used\n        inspectTaskModelResumeAvailableOnDB(connectionOnDBList);\n        statusCallback.onPending();\n    }\n\n    @Override\n    public void run() {\n        try {\n            Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);\n\n            // status checkout\n            if (model.getStatus() != FileDownloadStatus.pending) {\n                if (model.getStatus() == FileDownloadStatus.paused) {\n                    if (FileDownloadLog.NEED_LOG) {\n                        /**\n                         * @see FileDownloadThreadPool#cancel(int), the invoking simultaneously\n                         * with here. And this area is invoking before there, so,\n                         * {@code cancel(int)} is fail.\n                         *\n                         * High concurrent cause.\n                         */\n                        FileDownloadLog.d(this, \"High concurrent cause, start runnable but \"\n                                + \"already paused %d\", model.getId());\n                    }\n\n                } else {\n                    onError(new RuntimeException(\n                            FileDownloadUtils.formatString(\"Task[%d] can't start the download\"\n                                            + \" runnable, because its status is %d not %d\",\n                                    model.getId(), model.getStatus(), FileDownloadStatus.pending)));\n                }\n                return;\n            }\n\n            if (!paused) {\n                statusCallback.onStartThread();\n            }\n\n            do {\n                if (paused) {\n                    if (FileDownloadLog.NEED_LOG) {\n                        /**\n                         * @see FileDownloadThreadPool#cancel(int), the invoking simultaneously\n                         * with here. And this area is invoking before there, so,\n                         * {@code cancel(int)} is fail.\n                         *\n                         * High concurrent cause.\n                         */\n                        FileDownloadLog.d(this, \"High concurrent cause, start runnable but \"\n                                + \"already paused %d\", model.getId());\n                    }\n                    return;\n                }\n\n                try {\n                    // 1. check env state\n                    checkupBeforeConnect();\n\n                    // 2. trial connect\n                    trialConnect();\n\n                    // 3. reuse same task\n                    checkupAfterGetFilename();\n\n                    // 4. check local resume model\n                    final List<ConnectionModel> connectionOnDBList = database\n                            .findConnectionModel(model.getId());\n                    inspectTaskModelResumeAvailableOnDB(connectionOnDBList);\n\n                    if (paused) {\n                        model.setStatus(FileDownloadStatus.paused);\n                        return;\n                    }\n\n                    final long totalLength = model.getTotal();\n\n                    // 5. pre-allocate if need.\n                    handlePreAllocate(totalLength, model.getTempFilePath());\n\n                    // 6. calculate block count\n                    final int connectionCount = calcConnectionCount(totalLength);\n                    if (connectionCount <= 0) {\n                        throw new IllegalAccessException(FileDownloadUtils\n                                .formatString(\"invalid connection count %d, the connection count\"\n                                        + \" must be larger than 0\", connectionCount));\n                    }\n\n                    if (totalLength == 0) {\n                        return;\n                    }\n\n                    if (paused) {\n                        model.setStatus(FileDownloadStatus.paused);\n                        return;\n                    }\n\n                    // 7. start real connect and fetch to local filesystem\n                    isSingleConnection = connectionCount == 1;\n                    if (isSingleConnection) {\n                        // single connection\n                        realDownloadWithSingleConnection(totalLength);\n                    } else {\n                        // multiple connection\n                        statusCallback.onMultiConnection();\n                        if (isResumeAvailableOnDB) {\n                            realDownloadWithMultiConnectionFromResume(connectionCount,\n                                    connectionOnDBList);\n                        } else {\n                            realDownloadWithMultiConnectionFromBeginning(totalLength,\n                                    connectionCount);\n                        }\n                    }\n\n                } catch (IOException | IllegalAccessException\n                        | InterruptedException | IllegalArgumentException\n                        | FileDownloadSecurityException\n                        | FileDownloadGiveUpRetryException e) {\n                    if (isRetry(e)) {\n                        onRetry(e);\n                        continue;\n                    } else {\n                        onError(e);\n                    }\n                } catch (DiscardSafely discardSafely) {\n                    return;\n                } catch (RetryDirectly retryDirectly) {\n                    model.setStatus(FileDownloadStatus.retry);\n                    continue;\n                }\n\n                break;\n            } while (true);\n        } finally {\n            statusCallback.discardAllMessage();\n\n            if (paused) {\n                statusCallback.onPausedDirectly();\n            } else if (error) {\n                statusCallback.onErrorDirectly(errorException);\n            } else {\n                try {\n                    statusCallback.onCompletedDirectly();\n                } catch (IOException e) {\n                    statusCallback.onErrorDirectly(e);\n                }\n            }\n\n            alive.set(false);\n        }\n    }\n\n    private int calcConnectionCount(long totalLength) {\n        if (isMultiConnectionAvailable()) {\n            if (isResumeAvailableOnDB) {\n                return model.getConnectionCount();\n            } else {\n                return CustomComponentHolder.getImpl()\n                        .determineConnectionCount(model.getId(), model.getUrl(),\n                                model.getPath(), totalLength);\n            }\n        } else {\n            return 1;\n        }\n    }\n\n    // the trial connection is for: 1. etag verify; 2. partial support verify.\n    private void trialConnect() throws IOException, RetryDirectly, IllegalAccessException,\n            FileDownloadSecurityException {\n        FileDownloadConnection trialConnection = null;\n        try {\n            final ConnectionProfile trialConnectionProfile;\n            if (isNeedForceDiscardRange) {\n                trialConnectionProfile = ConnectionProfile.ConnectionProfileBuild\n                        .buildTrialConnectionProfileNoRange();\n            } else {\n                trialConnectionProfile = ConnectionProfile.ConnectionProfileBuild\n                        .buildTrialConnectionProfile();\n            }\n            final ConnectTask trialConnectTask = new ConnectTask.Builder()\n                    .setDownloadId(model.getId())\n                    .setUrl(model.getUrl())\n                    .setEtag(model.getETag())\n                    .setHeader(userRequestHeader)\n                    .setConnectionProfile(trialConnectionProfile)\n                    .build();\n            trialConnection = trialConnectTask.connect();\n            handleTrialConnectResult(trialConnectTask.getRequestHeader(),\n                    trialConnectTask, trialConnection);\n\n        } finally {\n            if (trialConnection != null) trialConnection.ending();\n        }\n    }\n\n    private boolean isMultiConnectionAvailable() {\n        //noinspection SimplifiableIfStatement\n        if (isResumeAvailableOnDB && model.getConnectionCount() <= 1) {\n            return false;\n        }\n\n        return acceptPartial && supportSeek && !isChunked;\n    }\n\n    private int determineConnectionCount() {\n        return defaultConnectionCount;\n    }\n\n    void inspectTaskModelResumeAvailableOnDB(List<ConnectionModel> connectionOnDBList) {\n        // check resume available\n        final long offset;\n        final int connectionCount = model.getConnectionCount();\n        final String tempFilePath = model.getTempFilePath();\n        final String targetFilePath = model.getTargetFilePath();\n        final boolean isMultiConnection = connectionCount > 1;\n        if (isNeedForceDiscardRange) {\n            offset = 0;\n        } else if (isMultiConnection && !supportSeek) {\n            // can't support seek for multi-connection is fatal problem, so discard resume.\n            offset = 0;\n        } else {\n            final boolean resumeAvailable = FileDownloadUtils\n                    .isBreakpointAvailable(model.getId(), model);\n            if (resumeAvailable) {\n                if (!supportSeek) {\n                    offset = new File(tempFilePath).length();\n                } else {\n                    if (isMultiConnection) {\n                        // when it is multi connections, the offset would be 0,\n                        // because it only store on the connection table.\n                        if (connectionCount != connectionOnDBList.size()) {\n                            // dirty data\n                            offset = 0;\n                        } else {\n                            offset = ConnectionModel.getTotalOffset(connectionOnDBList);\n                        }\n                    } else {\n                        offset = model.getSoFar();\n                    }\n                }\n            } else {\n                offset = 0;\n            }\n        }\n\n        model.setSoFar(offset);\n        isResumeAvailableOnDB = offset > 0;\n        if (!isResumeAvailableOnDB) {\n            database.removeConnections(model.getId());\n            FileDownloadUtils.deleteTaskFiles(targetFilePath, tempFilePath);\n        }\n    }\n\n    private void handleTrialConnectResult(Map<String, List<String>> requestHeader,\n                                          ConnectTask connectTask,\n                                          FileDownloadConnection connection)\n            throws IOException, RetryDirectly, IllegalArgumentException,\n            FileDownloadSecurityException {\n        final int id = model.getId();\n        final int code = connection.getResponseCode();\n\n        acceptPartial = FileDownloadUtils.isAcceptRange(code, connection);\n        final boolean onlyFromBeginning = (code == HttpURLConnection.HTTP_OK\n                || code == HttpURLConnection.HTTP_CREATED\n                || code == FileDownloadConnection.NO_RESPONSE_CODE);\n        final long totalLength = FileDownloadUtils.findInstanceLengthForTrial(connection);\n\n        final String oldEtag = model.getETag();\n        String newEtag = FileDownloadUtils.findEtag(id, connection);\n\n        // handle whether need retry because of etag is overdue\n        boolean isPreconditionFailed = false;\n        do {\n            if (code == HttpURLConnection.HTTP_PRECON_FAILED) {\n                isPreconditionFailed = true;\n                break;\n            }\n\n            if (oldEtag != null && !oldEtag.equals(newEtag)) {\n                // etag changed.\n                if (onlyFromBeginning || acceptPartial) {\n                    // 200 or 206\n                    isPreconditionFailed = true;\n                    break;\n                }\n            }\n\n            if (code == HttpURLConnection.HTTP_CREATED && connectTask.isRangeNotFromBeginning()) {\n                // The request has been fulfilled and has resulted in one or more new resources\n                // being created. mark this case is precondition failed for\n                // 1. checkout whether accept partial\n                // 2. 201 means new resources so range must be from beginning otherwise it can't\n                // match local range.\n                isPreconditionFailed = true;\n                break;\n            }\n\n            if (code == HTTP_REQUESTED_RANGE_NOT_SATISFIABLE) {\n                if (acceptPartial && totalLength >= 0) {\n                    // there is a special case: the server(such as Ali Cloud) doesn't follow the\n                    // agreement(https://tools.ietf.org/html/rfc7233), so they return a 416\n                    // response with Content-Range\n                    FileDownloadLog.w(this,\n                            \"get 416 but the Content-Range is returned, no need to retry\");\n                    break;\n                }\n                if (model.getSoFar() > 0) {\n                    // On the first connection range not satisfiable, there must something wrong,\n                    // so have to retry.\n                    isPreconditionFailed = true;\n                    FileDownloadLog.w(this, \"get 416, precondition failed and just retry\");\n                    break;\n                } else {\n                    // range is right, but get 416\n                    if (!isNeedForceDiscardRange) {\n                        // if range is still added, but range is right with 416 response, so we\n                        // discard range on header and try again\n                        isNeedForceDiscardRange = true;\n                        isPreconditionFailed = true;\n                        FileDownloadLog.w(this, \"get 416, precondition failed and need \"\n                                + \"to retry with discarding range\");\n                    }\n                }\n            }\n\n        } while (false);\n\n\n        if (isPreconditionFailed) {\n            // the file on remote is changed\n            if (isResumeAvailableOnDB) {\n                FileDownloadLog.w(this, \"there is precondition failed on this request[%d] \"\n                                + \"with old etag[%s]、new etag[%s]、response code is %d\",\n                        id, oldEtag, newEtag, code);\n            }\n\n            database.removeConnections(model.getId());\n            FileDownloadUtils.deleteTaskFiles(model.getTargetFilePath(), model.getTempFilePath());\n            isResumeAvailableOnDB = false;\n\n            if (oldEtag != null && oldEtag.equals(newEtag)) {\n                FileDownloadLog.w(this, \"the old etag[%s] is the same to the new etag[%s], \"\n                                + \"but the response status code is %d not Partial(206), so wo have\"\n                                + \" to start this task from very beginning for task[%d]!\",\n                        oldEtag, newEtag, code, id);\n                newEtag = null;\n            }\n\n            model.setSoFar(0);\n            model.setTotal(0);\n            model.setETag(newEtag);\n            model.resetConnectionCount();\n\n            database.updateOldEtagOverdue(id, model.getETag(), model.getSoFar(), model.getTotal(),\n                    model.getConnectionCount());\n\n            // retry to check whether support partial or not.\n            throw new RetryDirectly();\n        }\n\n        redirectedUrl = connectTask.getFinalRedirectedUrl();\n        if (acceptPartial || onlyFromBeginning) {\n\n\n            // update model\n            String fileName = null;\n            if (model.isPathAsDirectory()) {\n                // filename\n                fileName = FileDownloadUtils.findFilename(connection, model.getUrl());\n            }\n            isChunked = (totalLength == TOTAL_VALUE_IN_CHUNKED_RESOURCE);\n\n            // callback\n            statusCallback.onConnected(isResumeAvailableOnDB && acceptPartial,\n                    totalLength, newEtag, fileName);\n\n        } else {\n            throw new FileDownloadHttpException(code,\n                    requestHeader, connection.getResponseHeaderFields());\n        }\n    }\n\n    private void realDownloadWithSingleConnection(final long totalLength)\n            throws IOException, IllegalAccessException {\n\n        // connect\n        final ConnectionProfile profile;\n        if (!acceptPartial) {\n            model.setSoFar(0);\n            profile = ConnectionProfile.ConnectionProfileBuild\n                    .buildBeginToEndConnectionProfile(totalLength);\n        } else {\n            profile = ConnectionProfile.ConnectionProfileBuild\n                    .buildToEndConnectionProfile(model.getSoFar(), model.getSoFar(),\n                            totalLength - model.getSoFar());\n        }\n\n        singleDownloadRunnable = new DownloadRunnable.Builder()\n                .setId(model.getId())\n                .setConnectionIndex(-1)\n                .setCallback(this)\n                .setUrl(model.getUrl())\n                .setEtag(model.getETag())\n                .setHeader(userRequestHeader)\n                .setWifiRequired(isWifiRequired)\n                .setConnectionModel(profile)\n                .setPath(model.getTempFilePath())\n                .build();\n\n        model.setConnectionCount(1);\n        database.updateConnectionCount(model.getId(), 1);\n        if (paused) {\n            model.setStatus(FileDownloadStatus.paused);\n            singleDownloadRunnable.pause();\n        } else {\n            singleDownloadRunnable.run();\n        }\n    }\n\n    private void realDownloadWithMultiConnectionFromResume(final int connectionCount,\n                                                           List<ConnectionModel> modelList)\n            throws InterruptedException {\n        if (connectionCount <= 1 || modelList.size() != connectionCount) {\n            throw new IllegalArgumentException();\n        }\n\n        fetchWithMultipleConnection(modelList, model.getTotal());\n    }\n\n    private void realDownloadWithMultiConnectionFromBeginning(final long totalLength,\n                                                              final int connectionCount)\n            throws InterruptedException {\n        long startOffset = 0;\n        final long eachRegion = totalLength / connectionCount;\n        final int id = model.getId();\n\n        final List<ConnectionModel> connectionModelList = new ArrayList<>();\n\n        for (int i = 0; i < connectionCount; i++) {\n\n            final long endOffset;\n            if (i == connectionCount - 1) {\n                // avoid float precision error\n                endOffset = ConnectionProfile.RANGE_INFINITE;\n            } else {\n                // [startOffset, endOffset)\n                endOffset = startOffset + eachRegion - 1;\n            }\n\n            final ConnectionModel connectionModel = new ConnectionModel();\n            connectionModel.setId(id);\n            connectionModel.setIndex(i);\n            connectionModel.setStartOffset(startOffset);\n            connectionModel.setCurrentOffset(startOffset);\n            connectionModel.setEndOffset(endOffset);\n            connectionModelList.add(connectionModel);\n\n            database.insertConnectionModel(connectionModel);\n            startOffset += eachRegion;\n        }\n\n        model.setConnectionCount(connectionCount);\n        database.updateConnectionCount(id, connectionCount);\n\n        fetchWithMultipleConnection(connectionModelList, totalLength);\n    }\n\n\n    private void fetchWithMultipleConnection(final List<ConnectionModel> connectionModelList,\n                                             final long totalLength) throws InterruptedException {\n        final int id = model.getId();\n        final String etag = model.getETag();\n        final String url = redirectedUrl != null ? redirectedUrl : model.getUrl();\n        final String path = model.getTempFilePath();\n\n        if (FileDownloadLog.NEED_LOG) {\n            FileDownloadLog.d(this,\n                    \"fetch data with multiple connection(count: [%d]) for task[%d] totalLength[%d]\",\n                    connectionModelList.size(), id, totalLength);\n        }\n\n        long totalOffset = 0;\n\n        // why not with etag when not resume from the database? because do this can avoid\n        // precondition failed on separate downloading.\n        final boolean withEtag = isResumeAvailableOnDB;\n        for (ConnectionModel connectionModel : connectionModelList) {\n            final long contentLength;\n            if (connectionModel.getEndOffset() == ConnectionProfile.RANGE_INFINITE) {\n                // must be the last one\n                contentLength = totalLength - connectionModel.getCurrentOffset();\n            } else {\n                contentLength = connectionModel.getEndOffset() - connectionModel\n                        .getCurrentOffset() + 1;\n            }\n\n            totalOffset += (connectionModel.getCurrentOffset() - connectionModel.getStartOffset());\n\n            if (contentLength == 0) {\n                // [start, end), offset contain the start one, so need - 1.\n                // it has already done, so pass.\n                if (FileDownloadLog.NEED_LOG) {\n                    FileDownloadLog.d(this, \"pass connection[%d-%d], because it has been completed\",\n                            connectionModel.getId(), connectionModel.getIndex());\n                }\n                continue;\n            }\n\n            final DownloadRunnable.Builder builder = new DownloadRunnable.Builder();\n\n            final ConnectionProfile connectionProfile = ConnectionProfile.ConnectionProfileBuild\n                    .buildConnectionProfile(\n                            connectionModel.getStartOffset(), connectionModel.getCurrentOffset(),\n                            connectionModel.getEndOffset(), contentLength);\n\n            final DownloadRunnable runnable = builder\n                    .setId(id)\n                    .setConnectionIndex(connectionModel.getIndex())\n                    .setCallback(this)\n                    .setUrl(url)\n                    .setEtag(withEtag ? etag : null)\n                    .setHeader(userRequestHeader)\n                    .setWifiRequired(isWifiRequired)\n                    .setConnectionModel(connectionProfile)\n                    .setPath(path)\n                    .build();\n\n            if (FileDownloadLog.NEED_LOG) {\n                FileDownloadLog.d(this, \"enable multiple connection: %s\", connectionModel);\n            }\n\n            if (runnable == null) {\n                throw new IllegalArgumentException(\"the download runnable must not be null!\");\n            }\n\n            downloadRunnableList.add(runnable);\n        }\n\n        if (totalOffset != model.getSoFar()) {\n            FileDownloadLog.w(this, \"correct the sofar[%d] from connection table[%d]\",\n                    model.getSoFar(), totalOffset);\n            model.setSoFar(totalOffset);\n        }\n\n        List<Callable<Object>> subTasks = new ArrayList<>(downloadRunnableList.size());\n        for (DownloadRunnable runnable : downloadRunnableList) {\n            if (paused) {\n                runnable.pause();\n                continue;\n            }\n            subTasks.add(Executors.callable(runnable));\n        }\n        if (paused) {\n            model.setStatus(FileDownloadStatus.paused);\n            return;\n        }\n\n        List<Future<Object>> subTaskFutures = DOWNLOAD_EXECUTOR.invokeAll(subTasks);\n        if (FileDownloadLog.NEED_LOG) {\n            for (Future<Object> future : subTaskFutures) {\n                FileDownloadLog.d(this, \"finish sub-task for [%d] %B %B\",\n                        id, future.isDone(), future.isCancelled());\n            }\n        }\n    }\n\n    private void handlePreAllocate(long totalLength, String path)\n            throws IOException, IllegalAccessException {\n\n        FileDownloadOutputStream outputStream = null;\n        try {\n\n            if (totalLength != TOTAL_VALUE_IN_CHUNKED_RESOURCE) {\n                outputStream = FileDownloadUtils.createOutputStream(model.getTempFilePath());\n                final long breakpointBytes = new File(path).length();\n                final long requiredSpaceBytes = totalLength - breakpointBytes;\n\n                final long freeSpaceBytes = FileDownloadUtils.getFreeSpaceBytes(path);\n\n                if (freeSpaceBytes < requiredSpaceBytes) {\n                    // throw a out of space exception.\n                    throw new FileDownloadOutOfSpaceException(freeSpaceBytes,\n                            requiredSpaceBytes, breakpointBytes);\n                } else if (!FileDownloadProperties.getImpl().fileNonPreAllocation) {\n                    // pre allocate.\n                    outputStream.setLength(totalLength);\n                }\n            }\n        } finally {\n            if (outputStream != null) outputStream.close();\n        }\n    }\n\n    private long lastCallbackBytes = 0;\n    private long lastCallbackTimestamp = 0;\n\n    private long lastUpdateBytes = 0;\n    private long lastUpdateTimestamp = 0;\n\n    @Override\n    public void onProgress(long increaseBytes) {\n        if (paused) return;\n\n        statusCallback.onProgress(increaseBytes);\n    }\n\n    @Override\n    public void onCompleted(DownloadRunnable doneRunnable, long startOffset, long endOffset) {\n        if (paused) {\n            if (FileDownloadLog.NEED_LOG) {\n                FileDownloadLog.d(this, \"the task[%d] has already been paused, so pass the\"\n                        + \" completed callback\", model.getId());\n            }\n            return;\n        }\n\n        final int doneConnectionIndex = doneRunnable.connectionIndex;\n        if (FileDownloadLog.NEED_LOG) {\n            FileDownloadLog.d(this, \"the connection has been completed(%d): [%d, %d)  %d\",\n                    doneConnectionIndex, startOffset, endOffset, model.getTotal());\n        }\n\n        if (isSingleConnection) {\n            if (startOffset != 0 && endOffset != model.getTotal()) {\n                FileDownloadLog.e(this, \"the single task not completed corrected(%d, %d != %d) \"\n                        + \"for task(%d)\", startOffset, endOffset, model.getTotal(), model.getId());\n            }\n        } else {\n            synchronized (downloadRunnableList) {\n                downloadRunnableList.remove(doneRunnable);\n            }\n        }\n    }\n\n    @Override\n    public boolean isRetry(Exception exception) {\n        if (exception instanceof FileDownloadHttpException) {\n            final FileDownloadHttpException httpException = (FileDownloadHttpException) exception;\n\n            final int code = httpException.getCode();\n\n            if (isSingleConnection && code == HTTP_REQUESTED_RANGE_NOT_SATISFIABLE) {\n                if (!isTriedFixRangeNotSatisfiable) {\n                    FileDownloadUtils\n                            .deleteTaskFiles(model.getTargetFilePath(), model.getTempFilePath());\n                    isTriedFixRangeNotSatisfiable = true;\n                    return true;\n                }\n            }\n        }\n\n        return validRetryTimes > 0 && !(exception instanceof FileDownloadGiveUpRetryException);\n    }\n\n    @Override\n    public void onError(Exception exception) {\n        error = true;\n        errorException = exception;\n\n        if (paused) {\n            if (FileDownloadLog.NEED_LOG) {\n                FileDownloadLog.d(this, \"the task[%d] has already been paused, so pass the\"\n                        + \" error callback\", model.getId());\n            }\n            return;\n        }\n\n        // discard all\n        @SuppressWarnings(\"unchecked\") ArrayList<DownloadRunnable> discardList =\n                (ArrayList<DownloadRunnable>) downloadRunnableList.clone();\n        for (DownloadRunnable runnable : discardList) {\n            if (runnable != null) {\n                runnable.discard();\n                // if runnable is null, then that one must be completed and removed\n            }\n        }\n    }\n\n    @Override\n    public void onRetry(Exception exception) {\n        if (paused) {\n            if (FileDownloadLog.NEED_LOG) {\n                FileDownloadLog.d(this, \"the task[%d] has already been paused, so pass the\"\n                        + \" retry callback\", model.getId());\n            }\n            return;\n        }\n\n        if (validRetryTimes-- < 0) {\n            FileDownloadLog.e(this, \"valid retry times is less than 0(%d) for download task(%d)\",\n                    validRetryTimes, model.getId());\n        }\n\n        statusCallback.onRetry(exception, validRetryTimes);\n    }\n\n    @Override\n    public void syncProgressFromCache() {\n        database.updateProgress(model.getId(), model.getSoFar());\n    }\n\n    private void checkupBeforeConnect()\n            throws FileDownloadGiveUpRetryException {\n\n        // 1. check whether need access-network-state permission?\n        if (isWifiRequired\n                && !FileDownloadUtils.checkPermission(Manifest.permission.ACCESS_NETWORK_STATE)) {\n            throw new FileDownloadGiveUpRetryException(\n                    FileDownloadUtils.formatString(\"Task[%d] can't start the download runnable,\"\n                                    + \" because this task require wifi, but user application \"\n                                    + \"nor current process has %s, so we can't check whether \"\n                                    + \"the network type connection.\", model.getId(),\n                            Manifest.permission.ACCESS_NETWORK_STATE));\n        }\n\n        // 2. check whether need wifi to download?\n        if (isWifiRequired && FileDownloadUtils.isNetworkNotOnWifiType()) {\n            throw new FileDownloadNetworkPolicyException();\n        }\n    }\n\n    private void checkupAfterGetFilename() throws RetryDirectly, DiscardSafely {\n        final int id = model.getId();\n\n        if (model.isPathAsDirectory()) {\n            // this scope for caring about the case of there is another task is provided\n            // the same path to store file and the same url.\n\n            final String targetFilePath = model.getTargetFilePath();\n\n            // get the ID after got the filename.\n            final int fileCaseId = FileDownloadUtils.generateId(model.getUrl(),\n                    targetFilePath);\n\n            // whether the file with the filename has been existed.\n            if (FileDownloadHelper.inspectAndInflowDownloaded(id,\n                    targetFilePath, isForceReDownload, false)) {\n                database.remove(id);\n                database.removeConnections(id);\n                throw new DiscardSafely();\n            }\n\n            final FileDownloadModel fileCaseModel = database.find(fileCaseId);\n\n            if (fileCaseModel != null) {\n                // the task with the same file name and url has been exist.\n\n                // whether the another task with the same file and url is downloading.\n                if (FileDownloadHelper.inspectAndInflowDownloading(id, fileCaseModel,\n                        threadPoolMonitor, false)) {\n                    //it has been post to upper layer the 'warn' message, so the current\n                    // task no need to continue download.\n                    database.remove(id);\n                    database.removeConnections(id);\n                    throw new DiscardSafely();\n                }\n\n                final List<ConnectionModel> connectionModelList = database\n                        .findConnectionModel(fileCaseId);\n\n                // the another task with the same file name and url is paused\n                database.remove(fileCaseId);\n                database.removeConnections(fileCaseId);\n                FileDownloadUtils.deleteTargetFile(model.getTargetFilePath());\n\n                if (FileDownloadUtils.isBreakpointAvailable(fileCaseId, fileCaseModel)) {\n                    model.setSoFar(fileCaseModel.getSoFar());\n                    model.setTotal(fileCaseModel.getTotal());\n                    model.setETag(fileCaseModel.getETag());\n                    model.setConnectionCount(fileCaseModel.getConnectionCount());\n                    database.update(model);\n\n                    // re connect to resume from breakpoint.\n                    if (connectionModelList != null) {\n                        for (ConnectionModel connectionModel : connectionModelList) {\n                            connectionModel.setId(id);\n                            database.insertConnectionModel(connectionModel);\n                        }\n                    }\n\n                    // retry\n                    throw new RetryDirectly();\n                }\n            }\n\n            // whether there is an another running task with the same target-file-path.\n            if (FileDownloadHelper.inspectAndInflowConflictPath(id, model.getSoFar(),\n                    model.getTempFilePath(),\n                    targetFilePath,\n                    threadPoolMonitor)) {\n                database.remove(id);\n                database.removeConnections(id);\n\n                throw new DiscardSafely();\n            }\n        }\n    }\n\n    public int getId() {\n        return model.getId();\n    }\n\n    public boolean isAlive() {\n        return alive.get() || this.statusCallback.isAlive();\n    }\n\n    public String getTempFilePath() {\n        return model.getTempFilePath();\n    }\n\n    class RetryDirectly extends Throwable {\n    }\n\n    class DiscardSafely extends Throwable {\n    }\n\n    public static class Builder {\n        private FileDownloadModel model;\n        private FileDownloadHeader header;\n        private IThreadPoolMonitor threadPoolMonitor;\n        private Integer minIntervalMillis;\n        private Integer callbackProgressMaxCount;\n        private Boolean isForceReDownload;\n        private Boolean isWifiRequired;\n        private Integer maxRetryTimes;\n\n        public Builder setModel(FileDownloadModel model) {\n            this.model = model;\n            return this;\n        }\n\n        public Builder setHeader(FileDownloadHeader header) {\n            this.header = header;\n            return this;\n        }\n\n        public Builder setThreadPoolMonitor(IThreadPoolMonitor threadPoolMonitor) {\n            this.threadPoolMonitor = threadPoolMonitor;\n            return this;\n        }\n\n        public Builder setMinIntervalMillis(Integer minIntervalMillis) {\n            this.minIntervalMillis = minIntervalMillis;\n            return this;\n        }\n\n        public Builder setCallbackProgressMaxCount(Integer callbackProgressMaxCount) {\n            this.callbackProgressMaxCount = callbackProgressMaxCount;\n            return this;\n        }\n\n        public Builder setForceReDownload(Boolean forceReDownload) {\n            isForceReDownload = forceReDownload;\n            return this;\n        }\n\n        public Builder setWifiRequired(Boolean wifiRequired) {\n            isWifiRequired = wifiRequired;\n            return this;\n        }\n\n        public Builder setMaxRetryTimes(Integer maxRetryTimes) {\n            this.maxRetryTimes = maxRetryTimes;\n            return this;\n        }\n\n        public DownloadLaunchRunnable build() {\n            if (model == null || threadPoolMonitor == null\n                    || minIntervalMillis == null || callbackProgressMaxCount == null\n                    || isForceReDownload == null || isWifiRequired == null\n                    || maxRetryTimes == null) {\n                throw new IllegalArgumentException();\n            }\n\n            return new DownloadLaunchRunnable(model, header, threadPoolMonitor,\n                    minIntervalMillis, callbackProgressMaxCount,\n                    isForceReDownload, isWifiRequired, maxRetryTimes);\n        }\n    }\n}\n"
  },
  {
    "path": "library/src/main/java/com/liulishuo/filedownloader/download/DownloadRunnable.java",
    "content": "/*\n * Copyright (c) 2015 LingoChamp Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *   http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.liulishuo.filedownloader.download;\n\nimport android.os.Process;\n\nimport com.liulishuo.filedownloader.connection.FileDownloadConnection;\nimport com.liulishuo.filedownloader.database.FileDownloadDatabase;\nimport com.liulishuo.filedownloader.exception.FileDownloadGiveUpRetryException;\nimport com.liulishuo.filedownloader.model.ConnectionModel;\nimport com.liulishuo.filedownloader.model.FileDownloadHeader;\nimport com.liulishuo.filedownloader.model.FileDownloadModel;\nimport com.liulishuo.filedownloader.util.FileDownloadLog;\nimport com.liulishuo.filedownloader.util.FileDownloadUtils;\n\nimport java.io.IOException;\nimport java.net.HttpURLConnection;\nimport java.net.SocketException;\nimport java.util.List;\n\n/**\n * The single download runnable used for establish one connection and fetch data from it.\n */\npublic class DownloadRunnable implements Runnable {\n\n    private final ConnectTask connectTask;\n    private final ProcessCallback callback;\n    private final String path;\n    private final boolean isWifiRequired;\n\n    private FetchDataTask fetchDataTask;\n\n    private volatile boolean paused;\n    private final int downloadId;\n    final int connectionIndex;\n\n    private DownloadRunnable(int id, int connectionIndex, ConnectTask connectTask,\n                             ProcessCallback callback, boolean isWifiRequired, String path) {\n        this.downloadId = id;\n        this.connectionIndex = connectionIndex;\n        this.paused = false;\n        this.callback = callback;\n        this.path = path;\n        this.connectTask = connectTask;\n        this.isWifiRequired = isWifiRequired;\n    }\n\n    public void pause() {\n        paused = true;\n        if (fetchDataTask != null) fetchDataTask.pause();\n    }\n\n    public void discard() {\n        pause();\n    }\n\n    @Override\n    public void run() {\n        Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);\n\n        FileDownloadConnection connection = null;\n        final long beginOffset = connectTask.getProfile().currentOffset;\n        boolean isConnected = false;\n        do {\n\n            try {\n                if (paused) {\n                    return;\n                }\n\n                isConnected = false;\n                connection = connectTask.connect();\n                final int code = connection.getResponseCode();\n\n                if (FileDownloadLog.NEED_LOG) {\n                    FileDownloadLog\n                            .d(this, \"the connection[%d] for %d, is connected %s with code[%d]\",\n                                    connectionIndex, downloadId, connectTask.getProfile(), code);\n                }\n\n                if (code != HttpURLConnection.HTTP_PARTIAL && code != HttpURLConnection.HTTP_OK) {\n                    throw new SocketException(FileDownloadUtils.\n                            formatString(\n                                    \"Connection failed with request[%s] response[%s] \"\n                                            + \"http-state[%d] on task[%d-%d], which is changed\"\n                                            + \" after verify connection, so please try again.\",\n                                    connectTask.getRequestHeader(),\n                                    connection.getResponseHeaderFields(),\n                                    code, downloadId, connectionIndex));\n                }\n\n                isConnected = true;\n                final FetchDataTask.Builder builder = new FetchDataTask.Builder();\n\n                if (paused) return;\n                fetchDataTask = builder\n                        .setDownloadId(downloadId)\n                        .setConnectionIndex(connectionIndex)\n                        .setCallback(callback)\n                        .setHost(this)\n                        .setWifiRequired(isWifiRequired)\n                        .setConnection(connection)\n                        .setConnectionProfile(this.connectTask.getProfile())\n                        .setPath(path)\n                        .build();\n\n                fetchDataTask.run();\n\n                if (paused) fetchDataTask.pause();\n                break;\n\n            } catch (IllegalAccessException | IOException | FileDownloadGiveUpRetryException\n                    | IllegalArgumentException e) {\n                if (callback.isRetry(e)) {\n                    if (isConnected && fetchDataTask == null) {\n                        // connected but create fetch data task failed, give up directly.\n                        FileDownloadLog.w(this, \"it is valid to retry and connection is valid but\"\n                                + \" create fetch-data-task failed, so give up directly with %s\", e);\n                        callback.onError(e);\n                        break;\n                    } else {\n                        if (fetchDataTask != null) {\n                            //update currentOffset in ConnectionProfile\n                            final long downloadedOffset = getDownloadedOffset();\n                            if (downloadedOffset > 0) {\n                                connectTask.updateConnectionProfile(downloadedOffset);\n                            }\n                        }\n                        callback.onRetry(e);\n                    }\n                } else {\n                    callback.onError(e);\n                    break;\n                }\n\n            } finally {\n                if (connection != null) connection.ending();\n            }\n        } while (true);\n\n    }\n\n    private long getDownloadedOffset() {\n        final FileDownloadDatabase database = CustomComponentHolder.getImpl().getDatabaseInstance();\n        if (connectionIndex >= 0) {\n            // is multi connection\n            List<ConnectionModel> connectionModels = database.findConnectionModel(downloadId);\n            for (ConnectionModel connectionModel : connectionModels) {\n                if (connectionModel.getIndex() == connectionIndex) {\n                    return connectionModel.getCurrentOffset();\n                }\n            }\n        } else {\n            // is single connection\n            final FileDownloadModel downloadModel = database.find(downloadId);\n            if (downloadModel != null) return downloadModel.getSoFar();\n        }\n        return 0;\n    }\n\n    public static class Builder {\n        private final ConnectTask.Builder connectTaskBuilder = new ConnectTask.Builder();\n        private ProcessCallback callback;\n        private String path;\n        private Boolean isWifiRequired;\n        private Integer connectionIndex;\n\n\n        public Builder setCallback(ProcessCallback callback) {\n            this.callback = callback;\n            return this;\n        }\n\n        public Builder setId(int id) {\n            connectTaskBuilder.setDownloadId(id);\n            return this;\n        }\n\n        public Builder setUrl(String url) {\n            connectTaskBuilder.setUrl(url);\n            return this;\n        }\n\n        public Builder setEtag(String etag) {\n            connectTaskBuilder.setEtag(etag);\n            return this;\n        }\n\n        public Builder setHeader(FileDownloadHeader header) {\n            connectTaskBuilder.setHeader(header);\n            return this;\n        }\n\n        public Builder setConnectionModel(ConnectionProfile model) {\n            connectTaskBuilder.setConnectionProfile(model);\n            return this;\n        }\n\n        public Builder setPath(String path) {\n            this.path = path;\n            return this;\n        }\n\n        public Builder setWifiRequired(boolean wifiRequired) {\n            isWifiRequired = wifiRequired;\n            return this;\n        }\n\n        public Builder setConnectionIndex(Integer connectionIndex) {\n            this.connectionIndex = connectionIndex;\n            return this;\n        }\n\n        public DownloadRunnable build() {\n            if (callback == null || path == null || isWifiRequired == null\n                    || connectionIndex == null) {\n                throw new IllegalArgumentException(FileDownloadUtils.formatString(\"%s %s %B\",\n                        callback, path, isWifiRequired));\n            }\n\n            final ConnectTask connectTask = connectTaskBuilder.build();\n            return new DownloadRunnable(connectTask.downloadId, connectionIndex, connectTask,\n                    callback, isWifiRequired, path);\n        }\n\n        DownloadRunnable buildForTest(ConnectTask connectTask) {\n            return new DownloadRunnable(connectTask.downloadId, 0, connectTask,\n                    callback, false, \"\");\n        }\n\n    }\n}\n"
  },
  {
    "path": "library/src/main/java/com/liulishuo/filedownloader/download/DownloadStatusCallback.java",
    "content": "/*\n * Copyright (c) 2015 LingoChamp Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *   http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.liulishuo.filedownloader.download;\n\nimport android.database.sqlite.SQLiteFullException;\nimport android.os.Build;\nimport android.os.Handler;\nimport android.os.HandlerThread;\nimport android.os.Message;\nimport android.os.SystemClock;\n\nimport com.liulishuo.filedownloader.database.FileDownloadDatabase;\nimport com.liulishuo.filedownloader.exception.FileDownloadGiveUpRetryException;\nimport com.liulishuo.filedownloader.exception.FileDownloadOutOfSpaceException;\nimport com.liulishuo.filedownloader.message.MessageSnapshotFlow;\nimport com.liulishuo.filedownloader.message.MessageSnapshotTaker;\nimport com.liulishuo.filedownloader.model.FileDownloadModel;\nimport com.liulishuo.filedownloader.model.FileDownloadStatus;\nimport com.liulishuo.filedownloader.services.FileDownloadBroadcastHandler;\nimport com.liulishuo.filedownloader.util.FileDownloadLog;\nimport com.liulishuo.filedownloader.util.FileDownloadProperties;\nimport com.liulishuo.filedownloader.util.FileDownloadUtils;\n\nimport java.io.File;\nimport java.io.IOException;\nimport java.util.concurrent.TimeUnit;\nimport java.util.concurrent.atomic.AtomicBoolean;\nimport java.util.concurrent.atomic.AtomicLong;\nimport java.util.concurrent.locks.LockSupport;\n\nimport static com.liulishuo.filedownloader.download.FetchDataTask.BUFFER_SIZE;\nimport static com.liulishuo.filedownloader.model.FileDownloadModel.TOTAL_VALUE_IN_CHUNKED_RESOURCE;\n\n/**\n * handle all events sync to DB/filesystem and callback to user.\n */\npublic class DownloadStatusCallback implements Handler.Callback {\n\n    private final FileDownloadModel model;\n    private final FileDownloadDatabase database;\n    private final ProcessParams processParams;\n    private final int maxRetryTimes;\n\n\n    private static final int CALLBACK_SAFE_MIN_INTERVAL_BYTES = 1/*byte*/;\n    private static final int CALLBACK_SAFE_MIN_INTERVAL_MILLIS = 5/*ms*/;\n    private static final int NO_ANY_PROGRESS_CALLBACK = -1;\n\n    private final int callbackProgressMinInterval;\n    private final int callbackProgressMaxCount;\n    private long callbackMinIntervalBytes;\n\n    private Handler handler;\n    private HandlerThread handlerThread;\n\n    DownloadStatusCallback(FileDownloadModel model,\n                           int maxRetryTimes, final int minIntervalMillis,\n                           int callbackProgressMaxCount) {\n        this.model = model;\n        this.database = CustomComponentHolder.getImpl().getDatabaseInstance();\n        this.callbackProgressMinInterval = minIntervalMillis < CALLBACK_SAFE_MIN_INTERVAL_MILLIS\n                ? CALLBACK_SAFE_MIN_INTERVAL_MILLIS : minIntervalMillis;\n        this.callbackProgressMaxCount = callbackProgressMaxCount;\n        this.processParams = new ProcessParams();\n        this.maxRetryTimes = maxRetryTimes;\n    }\n\n    public boolean isAlive() {\n        return handlerThread != null && handlerThread.isAlive();\n    }\n\n    private volatile boolean handlingMessage = false;\n    private volatile Thread parkThread;\n\n    void discardAllMessage() {\n        if (handler != null) {\n            handler.removeCallbacksAndMessages(null);\n            handlerThread.quit();\n\n            parkThread = Thread.currentThread();\n            while (handlingMessage) {\n                LockSupport.parkNanos(TimeUnit.MILLISECONDS.toNanos(100));\n            }\n            parkThread = null;\n        }\n    }\n\n    public void onPending() {\n        model.setStatus(FileDownloadStatus.pending);\n\n        // direct\n        database.updatePending(model.getId());\n        onStatusChanged(FileDownloadStatus.pending);\n    }\n\n    void onStartThread() {\n        // direct\n        model.setStatus(FileDownloadStatus.started);\n        onStatusChanged(FileDownloadStatus.started);\n        database.onTaskStart(model.getId());\n    }\n\n    void onConnected(boolean isResume, long totalLength, String etag, String fileName) throws\n            IllegalArgumentException {\n        final String oldEtag = model.getETag();\n        if (oldEtag != null && !oldEtag.equals(etag)) throw\n                new IllegalArgumentException(FileDownloadUtils.formatString(\"callback \"\n                                + \"onConnected must with precondition succeed, but the etag is \"\n                                + \"changes(%s != %s)\",\n                        etag, oldEtag));\n\n        // direct\n        processParams.setResuming(isResume);\n\n        model.setStatus(FileDownloadStatus.connected);\n        model.setTotal(totalLength);\n        model.setETag(etag);\n        model.setFilename(fileName);\n\n        database.updateConnected(model.getId(), totalLength, etag, fileName);\n        onStatusChanged(FileDownloadStatus.connected);\n\n        callbackMinIntervalBytes = calculateCallbackMinIntervalBytes(totalLength,\n                callbackProgressMaxCount);\n        needSetProcess.compareAndSet(false, true);\n    }\n\n    void onMultiConnection() {\n        handlerThread = new HandlerThread(\"source-status-callback\");\n        handlerThread.start();\n        handler = new Handler(handlerThread.getLooper(), this);\n    }\n\n    private volatile long lastCallbackTimestamp = 0;\n\n    private final AtomicLong callbackIncreaseBuffer = new AtomicLong();\n    private final AtomicBoolean needCallbackProgressToUser = new AtomicBoolean(false);\n\n    void onProgress(long increaseBytes) {\n        callbackIncreaseBuffer.addAndGet(increaseBytes);\n        model.increaseSoFar(increaseBytes);\n\n        final long now = SystemClock.elapsedRealtime();\n\n        inspectNeedCallbackToUser(now);\n\n        if (handler == null) {\n            // direct\n            handleProgress();\n        } else if (needCallbackProgressToUser.get()) {\n            // flow\n            sendMessage(handler.obtainMessage(FileDownloadStatus.progress));\n        }\n    }\n\n    void onRetry(Exception exception, int remainRetryTimes) {\n        this.callbackIncreaseBuffer.set(0);\n\n        if (handler == null) {\n            // direct\n            handleRetry(exception, remainRetryTimes);\n        } else {\n            // flow\n            sendMessage(handler.obtainMessage(FileDownloadStatus.retry, remainRetryTimes, 0,\n                    exception));\n        }\n    }\n\n\n    void onPausedDirectly() {\n        handlePaused();\n    }\n\n    void onErrorDirectly(Exception exception) {\n        handleError(exception);\n    }\n\n    void onCompletedDirectly() throws IOException {\n        if (interceptBeforeCompleted()) {\n            return;\n        }\n\n        handleCompleted();\n    }\n\n    private static final String ALREADY_DEAD_MESSAGE = \"require callback %d but the host thread\"\n            + \" of the flow has already dead, what is occurred because of there are several reason\"\n            + \" can final this flow on different thread.\";\n\n    private synchronized void sendMessage(Message message) {\n\n        if (!handlerThread.isAlive()) {\n            if (FileDownloadLog.NEED_LOG) {\n                FileDownloadLog.d(this, ALREADY_DEAD_MESSAGE, message.what);\n            }\n            return;\n        }\n\n        try {\n            handler.sendMessage(message);\n        } catch (IllegalStateException e) {\n            if (!handlerThread.isAlive()) {\n                if (FileDownloadLog.NEED_LOG) {\n                    FileDownloadLog.d(this, ALREADY_DEAD_MESSAGE, message.what);\n                }\n            } else {\n                // unknown error\n                throw e;\n            }\n        }\n    }\n\n    private static long calculateCallbackMinIntervalBytes(final long contentLength,\n                                                          final long callbackProgressMaxCount) {\n        if (callbackProgressMaxCount <= 0) {\n            return NO_ANY_PROGRESS_CALLBACK;\n        } else if (contentLength == TOTAL_VALUE_IN_CHUNKED_RESOURCE) {\n            return CALLBACK_SAFE_MIN_INTERVAL_BYTES;\n        } else {\n            final long minIntervalBytes = contentLength / (callbackProgressMaxCount);\n            return minIntervalBytes <= 0 ? CALLBACK_SAFE_MIN_INTERVAL_BYTES : minIntervalBytes;\n        }\n    }\n\n    private Exception exFiltrate(Exception ex) {\n        final String tempPath = model.getTempFilePath();\n        /**\n         * Only handle the case of Chunked resource, if it is not chunked, has already been handled\n         * in {@link #getOutputStream(boolean, long)}.\n         */\n        if ((model.isChunked() || FileDownloadProperties.getImpl().fileNonPreAllocation)\n                && ex instanceof IOException\n                && new File(tempPath).exists()) {\n            // chunked\n            final long freeSpaceBytes = FileDownloadUtils.getFreeSpaceBytes(tempPath);\n            if (freeSpaceBytes <= BUFFER_SIZE) {\n                // free space is not enough.\n                long downloadedSize = 0;\n                final File file = new File(tempPath);\n                if (!file.exists()) {\n                    FileDownloadLog.e(this, ex, \"Exception with: free \"\n                            + \"space isn't enough, and the target file not exist.\");\n                } else {\n                    downloadedSize = file.length();\n                }\n\n                if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.GINGERBREAD) {\n                    ex = new FileDownloadOutOfSpaceException(freeSpaceBytes, BUFFER_SIZE,\n                            downloadedSize, ex);\n                } else {\n                    ex = new FileDownloadOutOfSpaceException(freeSpaceBytes, BUFFER_SIZE,\n                            downloadedSize);\n                }\n\n            }\n        }\n\n        return ex;\n    }\n\n    private void handleSQLiteFullException(final SQLiteFullException sqLiteFullException) {\n        final int id = model.getId();\n        if (FileDownloadLog.NEED_LOG) {\n            FileDownloadLog.d(this, \"the data of the task[%d] is dirty, because the SQLite \"\n                            + \"full exception[%s], so remove it from the database directly.\",\n                    id, sqLiteFullException.toString());\n        }\n\n        model.setErrMsg(sqLiteFullException.toString());\n        model.setStatus(FileDownloadStatus.error);\n\n        database.remove(id);\n        database.removeConnections(id);\n    }\n\n    private void renameTempFile() throws IOException {\n        final String tempPath = model.getTempFilePath();\n        final String targetPath = model.getTargetFilePath();\n\n        final File tempFile = new File(tempPath);\n        boolean renameFailed = true;\n        try {\n            final File targetFile = new File(targetPath);\n\n            if (targetFile.exists()) {\n                final long oldTargetFileLength = targetFile.length();\n                if (!targetFile.delete()) {\n                    throw new IOException(FileDownloadUtils.formatString(\n                            \"Can't delete the old file([%s], [%d]), \"\n                                    + \"so can't replace it with the new downloaded one.\",\n                            targetPath, oldTargetFileLength\n                    ));\n                } else {\n                    FileDownloadLog.w(this, \"The target file([%s], [%d]) will be replaced with\"\n                                    + \" the new downloaded file[%d]\",\n                            targetPath, oldTargetFileLength, tempFile.length());\n                }\n            }\n\n            renameFailed = !tempFile.renameTo(targetFile);\n            if (renameFailed) {\n                throw new IOException(FileDownloadUtils.formatString(\n                        \"Can't rename the  temp downloaded file(%s) to the target file(%s)\",\n                        tempPath, targetPath\n                ));\n            }\n        } finally {\n            if (renameFailed && tempFile.exists()) {\n                if (!tempFile.delete()) {\n                    FileDownloadLog.w(this,\n                            \"delete the temp file(%s) failed, on completed downloading.\",\n                            tempPath);\n                }\n            }\n        }\n    }\n\n    @Override\n    public boolean handleMessage(Message msg) {\n        handlingMessage = true;\n        final int status = msg.what;\n\n        try {\n            switch (status) {\n                case FileDownloadStatus.progress:\n                    handleProgress();\n                    break;\n                case FileDownloadStatus.retry:\n                    handleRetry((Exception) msg.obj, msg.arg1);\n                    break;\n                default:\n                    // ignored.\n            }\n        } finally {\n            handlingMessage = false;\n            if (parkThread != null) LockSupport.unpark(parkThread);\n        }\n\n\n        return true;\n    }\n\n    private final AtomicBoolean needSetProcess = new AtomicBoolean(false);\n\n    private void handleProgress() {\n        if (model.getSoFar() == model.getTotal()) {\n            database.updateProgress(model.getId(), model.getSoFar());\n            return;\n        }\n\n        if (needSetProcess.compareAndSet(true, false)) {\n            if (FileDownloadLog.NEED_LOG) {\n                FileDownloadLog.i(this, \"handleProgress update model's status with progress\");\n            }\n            model.setStatus(FileDownloadStatus.progress);\n        }\n\n        if (needCallbackProgressToUser.compareAndSet(true, false)) {\n            if (FileDownloadLog.NEED_LOG) {\n                FileDownloadLog.i(this, \"handleProgress notify user progress status\");\n            }\n            onStatusChanged(FileDownloadStatus.progress);\n        }\n    }\n\n    private void handleCompleted() throws IOException {\n        renameTempFile();\n\n        model.setStatus(FileDownloadStatus.completed);\n\n        database.updateCompleted(model.getId(), model.getTotal());\n        database.removeConnections(model.getId());\n\n        onStatusChanged(FileDownloadStatus.completed);\n\n        if (FileDownloadProperties.getImpl().broadcastCompleted) {\n            FileDownloadBroadcastHandler.sendCompletedBroadcast(model);\n        }\n    }\n\n    private boolean interceptBeforeCompleted() {\n        if (model.isChunked()) {\n            model.setTotal(model.getSoFar());\n        } else if (model.getSoFar() != model.getTotal()) {\n            onErrorDirectly(new FileDownloadGiveUpRetryException(\n                    FileDownloadUtils.formatString(\"sofar[%d] not equal total[%d]\",\n                            model.getSoFar(), model.getTotal())));\n            return true;\n        }\n\n        return false;\n    }\n\n    private void handleRetry(final Exception exception, final int remainRetryTimes) {\n        Exception processEx = exFiltrate(exception);\n        processParams.setException(processEx);\n        processParams.setRetryingTimes(maxRetryTimes - remainRetryTimes);\n\n        model.setStatus(FileDownloadStatus.retry);\n        model.setErrMsg(processEx.toString());\n\n        database.updateRetry(model.getId(), processEx);\n        onStatusChanged(FileDownloadStatus.retry);\n    }\n\n    private void handlePaused() {\n        model.setStatus(FileDownloadStatus.paused);\n\n        database.updatePause(model.getId(), model.getSoFar());\n        onStatusChanged(FileDownloadStatus.paused);\n    }\n\n    private void handleError(Exception exception) {\n        Exception errProcessEx = exFiltrate(exception);\n\n        if (errProcessEx instanceof SQLiteFullException) {\n            // If the error is sqLite full exception already, no need to  update it to the database\n            // again.\n            handleSQLiteFullException((SQLiteFullException) errProcessEx);\n        } else {\n            // Normal case.\n            try {\n\n                model.setStatus(FileDownloadStatus.error);\n                model.setErrMsg(exception.toString());\n\n                database.updateError(model.getId(), errProcessEx, model.getSoFar());\n            } catch (SQLiteFullException fullException) {\n                errProcessEx = fullException;\n                handleSQLiteFullException((SQLiteFullException) errProcessEx);\n            }\n        }\n\n        processParams.setException(errProcessEx);\n        onStatusChanged(FileDownloadStatus.error);\n    }\n\n    private final AtomicBoolean isFirstCallback = new AtomicBoolean(true);\n\n    private void inspectNeedCallbackToUser(final long now) {\n        final boolean needCallback;\n        if (isFirstCallback.compareAndSet(true, false)) {\n            needCallback = true;\n        } else {\n            final long callbackTimeDelta = now - lastCallbackTimestamp;\n            needCallback = (callbackMinIntervalBytes != NO_ANY_PROGRESS_CALLBACK\n                    && callbackIncreaseBuffer.get() >= callbackMinIntervalBytes)\n                    && (callbackTimeDelta >= callbackProgressMinInterval);\n        }\n        if (needCallback && needCallbackProgressToUser.compareAndSet(false, true)) {\n            if (FileDownloadLog.NEED_LOG) {\n                FileDownloadLog.i(this, \"inspectNeedCallbackToUser need callback to user\");\n            }\n            lastCallbackTimestamp = now;\n            callbackIncreaseBuffer.set(0);\n        }\n    }\n\n    private void onStatusChanged(final byte status) {\n        // In current situation, it maybe invoke this method simultaneously between #onPause() and\n        // others.\n        if (status == FileDownloadStatus.paused) {\n            if (FileDownloadLog.NEED_LOG) {\n                /**\n                 * Already paused or the current status is paused.\n                 *\n                 * We don't need to call-back to Task in here, because the pause status has\n                 * already handled by {@link BaseDownloadTask#pause()} manually.\n                 *\n                 * In some case, it will arrive here by High concurrent cause.  For performance\n                 * more make sense.\n                 *\n                 * High concurrent cause.\n                 */\n                FileDownloadLog.d(this, \"High concurrent cause, Already paused and we don't \"\n                        + \"need to call-back to Task in here, %d\", model.getId());\n            }\n            return;\n        }\n\n        MessageSnapshotFlow.getImpl().inflow(\n                MessageSnapshotTaker.take(status, model, processParams));\n    }\n\n    public static class ProcessParams {\n        private boolean isResuming;\n        private Exception exception;\n        private int retryingTimes;\n\n        void setResuming(boolean isResuming) {\n            this.isResuming = isResuming;\n        }\n\n        public boolean isResuming() {\n            return this.isResuming;\n        }\n\n        void setException(Exception exception) {\n            this.exception = exception;\n        }\n\n        void setRetryingTimes(int retryingTimes) {\n            this.retryingTimes = retryingTimes;\n        }\n\n        public Exception getException() {\n            return exception;\n        }\n\n        public int getRetryingTimes() {\n            return retryingTimes;\n        }\n    }\n}\n"
  },
  {
    "path": "library/src/main/java/com/liulishuo/filedownloader/download/FetchDataTask.java",
    "content": "/*\n * Copyright (c) 2015 LingoChamp Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *   http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.liulishuo.filedownloader.download;\n\nimport android.os.SystemClock;\n\nimport com.liulishuo.filedownloader.connection.FileDownloadConnection;\nimport com.liulishuo.filedownloader.exception.FileDownloadGiveUpRetryException;\nimport com.liulishuo.filedownloader.exception.FileDownloadNetworkPolicyException;\nimport com.liulishuo.filedownloader.database.FileDownloadDatabase;\nimport com.liulishuo.filedownloader.stream.FileDownloadOutputStream;\nimport com.liulishuo.filedownloader.util.FileDownloadLog;\nimport com.liulishuo.filedownloader.util.FileDownloadUtils;\n\nimport java.io.IOException;\nimport java.io.InputStream;\n\nimport static com.liulishuo.filedownloader.model.FileDownloadModel.TOTAL_VALUE_IN_CHUNKED_RESOURCE;\n\n/**\n * Fetch data from the provided connection.\n */\npublic class FetchDataTask {\n\n    static final int BUFFER_SIZE = 1024 * 4;\n    private final ProcessCallback callback;\n\n    private final int downloadId;\n    private final int connectionIndex;\n    private final DownloadRunnable hostRunnable;\n    private final FileDownloadConnection connection;\n    private final boolean isWifiRequired;\n\n    private final long startOffset;\n    private final long endOffset;\n    private final long contentLength;\n    private final String path;\n\n    long currentOffset;\n    private FileDownloadOutputStream outputStream;\n\n    private volatile boolean paused;\n\n    public void pause() {\n        paused = true;\n    }\n\n    private FetchDataTask(FileDownloadConnection connection, ConnectionProfile connectionProfile,\n                          DownloadRunnable host, int id, int connectionIndex,\n                          boolean isWifiRequired, ProcessCallback callback, String path) {\n        this.callback = callback;\n        this.path = path;\n        this.connection = connection;\n        this.isWifiRequired = isWifiRequired;\n        this.hostRunnable = host;\n        this.connectionIndex = connectionIndex;\n        this.downloadId = id;\n        this.database = CustomComponentHolder.getImpl().getDatabaseInstance();\n\n        startOffset = connectionProfile.startOffset;\n        endOffset = connectionProfile.endOffset;\n        currentOffset = connectionProfile.currentOffset;\n        contentLength = connectionProfile.contentLength;\n    }\n\n    public void run() throws IOException, IllegalAccessException, IllegalArgumentException,\n            FileDownloadGiveUpRetryException {\n\n        if (paused) return;\n\n        long contentLength = FileDownloadUtils.findContentLength(connectionIndex, connection);\n        if (contentLength == TOTAL_VALUE_IN_CHUNKED_RESOURCE) {\n            contentLength = FileDownloadUtils.findContentLengthFromContentRange(connection);\n        }\n        if (contentLength == 0) {\n            throw new FileDownloadGiveUpRetryException(FileDownloadUtils.\n                    formatString(\n                            \"there isn't any content need to download on %d-%d with the \"\n                                    + \"content-length is 0\",\n                            downloadId, connectionIndex));\n        }\n\n        if (this.contentLength > 0 && contentLength != this.contentLength) {\n            final String range;\n            if (endOffset == ConnectionProfile.RANGE_INFINITE) {\n                range = FileDownloadUtils.formatString(\"range[%d-)\", currentOffset);\n            } else {\n                range = FileDownloadUtils.formatString(\"range[%d-%d)\", currentOffset, endOffset);\n            }\n            throw new FileDownloadGiveUpRetryException(FileDownloadUtils.\n                    formatString(\"require %s with contentLength(%d), but the \"\n                                    + \"backend response contentLength is %d on \"\n                                    + \"downloadId[%d]-connectionIndex[%d], please ask your backend \"\n                                    + \"dev to fix such problem.\",\n                            range, this.contentLength, contentLength, downloadId, connectionIndex));\n        }\n\n        final long fetchBeginOffset = currentOffset;\n        // start fetch\n        InputStream inputStream = null;\n        FileDownloadOutputStream outputStream = null;\n\n        try {\n            final boolean isSupportSeek = CustomComponentHolder.getImpl().isSupportSeek();\n            if (hostRunnable != null && !isSupportSeek) {\n                throw new IllegalAccessException(\n                        \"can't using multi-download when the output stream can't support seek\");\n            }\n\n            this.outputStream = outputStream = FileDownloadUtils.createOutputStream(path);\n            if (isSupportSeek) {\n                outputStream.seek(currentOffset);\n            }\n\n            if (FileDownloadLog.NEED_LOG) {\n                FileDownloadLog.d(this, \"start fetch(%d): range [%d, %d), seek to[%d]\",\n                        connectionIndex, startOffset, endOffset, currentOffset);\n            }\n\n            inputStream = connection.getInputStream();\n\n            byte[] buff = new byte[BUFFER_SIZE];\n\n            if (paused) return;\n\n            do {\n                int byteCount = inputStream.read(buff);\n                if (byteCount == -1) {\n                    break;\n                }\n\n                outputStream.write(buff, 0, byteCount);\n\n                currentOffset += byteCount;\n\n                // callback progress\n                callback.onProgress(byteCount);\n\n                checkAndSync();\n\n                // check status\n                if (paused) return;\n\n                if (isWifiRequired && FileDownloadUtils.isNetworkNotOnWifiType()) {\n                    throw new FileDownloadNetworkPolicyException();\n                }\n\n            } while (true);\n\n        } finally {\n\n            if (inputStream != null) {\n                try {\n                    inputStream.close();\n                } catch (IOException e) {\n                    e.printStackTrace();\n                }\n            }\n\n            try {\n                if (outputStream != null) sync();\n            } finally {\n                if (outputStream != null) {\n                    try {\n                        outputStream.close();\n                    } catch (IOException e) {\n                        e.printStackTrace();\n                    }\n                }\n            }\n\n        }\n\n        final long fetchedLength = currentOffset - fetchBeginOffset;\n        if (contentLength != TOTAL_VALUE_IN_CHUNKED_RESOURCE && contentLength != fetchedLength) {\n            throw new FileDownloadGiveUpRetryException(\n                    FileDownloadUtils.formatString(\"fetched length[%d] != content length[%d],\"\n                                    + \" range[%d, %d) offset[%d] fetch begin offset[%d]\",\n                            fetchedLength, contentLength,\n                            startOffset, endOffset, currentOffset, fetchBeginOffset));\n        }\n\n        // callback completed\n        callback.onCompleted(hostRunnable, startOffset, endOffset);\n    }\n\n    private final FileDownloadDatabase database;\n    private volatile long lastSyncBytes = 0;\n    private volatile long lastSyncTimestamp = 0;\n\n    private void checkAndSync() {\n        final long now = SystemClock.elapsedRealtime();\n        final long bytesDelta = currentOffset - lastSyncBytes;\n        final long timestampDelta = now - lastSyncTimestamp;\n\n        if (FileDownloadUtils.isNeedSync(bytesDelta, timestampDelta)) {\n            sync();\n\n            lastSyncBytes = currentOffset;\n            lastSyncTimestamp = now;\n        }\n    }\n\n    private void sync() {\n        final long startTimestamp = SystemClock.uptimeMillis();\n\n        boolean bufferPersistToDevice;\n        try {\n            outputStream.flushAndSync();\n            bufferPersistToDevice = true;\n        } catch (IOException e) {\n            bufferPersistToDevice = false;\n            if (FileDownloadLog.NEED_LOG) {\n                FileDownloadLog.d(this, \"Because of the system cannot guarantee that all \"\n                        + \"the buffers have been synchronized with physical media, or write to file\"\n                        + \"failed, we just not flushAndSync process to database too %s\", e);\n            }\n        }\n\n        if (bufferPersistToDevice) {\n            final boolean isBelongMultiConnection = connectionIndex >= 0;\n            if (isBelongMultiConnection) {\n                // only need update the connection table.\n                database.updateConnectionModel(downloadId, connectionIndex, currentOffset);\n            } else {\n                // only need update the filedownloader table.\n                callback.syncProgressFromCache();\n            }\n\n            if (FileDownloadLog.NEED_LOG) {\n                FileDownloadLog\n                        .d(this, \"require flushAndSync id[%d] index[%d] offset[%d], consume[%d]\",\n                                downloadId, connectionIndex, currentOffset,\n                                SystemClock.uptimeMillis() - startTimestamp);\n            }\n        }\n    }\n\n    public static class Builder {\n        DownloadRunnable downloadRunnable;\n        FileDownloadConnection connection;\n        ConnectionProfile connectionProfile;\n        ProcessCallback callback;\n        String path;\n        Boolean isWifiRequired;\n        Integer connectionIndex;\n        Integer downloadId;\n\n        public Builder setConnection(FileDownloadConnection connection) {\n            this.connection = connection;\n            return this;\n        }\n\n        public Builder setConnectionProfile(ConnectionProfile connectionProfile) {\n            this.connectionProfile = connectionProfile;\n            return this;\n        }\n\n        public Builder setCallback(ProcessCallback callback) {\n            this.callback = callback;\n            return this;\n        }\n\n        public Builder setPath(String path) {\n            this.path = path;\n            return this;\n        }\n\n        public Builder setWifiRequired(boolean wifiRequired) {\n            isWifiRequired = wifiRequired;\n            return this;\n        }\n\n        public Builder setHost(DownloadRunnable downloadRunnable) {\n            this.downloadRunnable = downloadRunnable;\n            return this;\n        }\n\n        public Builder setConnectionIndex(int connectionIndex) {\n            this.connectionIndex = connectionIndex;\n            return this;\n        }\n\n        public Builder setDownloadId(int downloadId) {\n            this.downloadId = downloadId;\n            return this;\n        }\n\n        public FetchDataTask build() throws IllegalArgumentException {\n            if (isWifiRequired == null || connection == null || connectionProfile == null\n                    || callback == null || path == null || downloadId == null\n                    || connectionIndex == null) {\n                throw new IllegalArgumentException();\n            }\n\n            return new FetchDataTask(connection, connectionProfile, downloadRunnable,\n                    downloadId, connectionIndex,\n                    isWifiRequired, callback, path);\n        }\n\n    }\n}\n"
  },
  {
    "path": "library/src/main/java/com/liulishuo/filedownloader/download/ProcessCallback.java",
    "content": "/*\n * Copyright (c) 2015 LingoChamp Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *   http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.liulishuo.filedownloader.download;\n\n/**\n * The process event callbacks.\n */\npublic interface ProcessCallback {\n\n    void onProgress(long increaseBytes);\n\n    void onCompleted(DownloadRunnable doneRunnable, long startOffset, long endOffset);\n\n    boolean isRetry(Exception exception);\n\n    void onError(Exception exception);\n\n    void onRetry(Exception exception);\n\n    void syncProgressFromCache();\n}\n"
  },
  {
    "path": "library/src/main/java/com/liulishuo/filedownloader/event/DownloadEventPoolImpl.java",
    "content": "/*\n * Copyright (c) 2015 LingoChamp Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *    http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.liulishuo.filedownloader.event;\n\nimport com.liulishuo.filedownloader.util.FileDownloadExecutors;\nimport com.liulishuo.filedownloader.util.FileDownloadLog;\n\nimport java.util.HashMap;\nimport java.util.LinkedList;\nimport java.util.concurrent.Executor;\n\n/**\n * Implementing actions for event pool.\n */\npublic class DownloadEventPoolImpl implements IDownloadEventPool {\n\n    private final Executor threadPool = FileDownloadExecutors.newDefaultThreadPool(10, \"EventPool\");\n\n    private final HashMap<String, LinkedList<IDownloadListener>> listenersMap = new HashMap<>();\n\n    @Override\n    public boolean addListener(final String eventId, final IDownloadListener listener) {\n        if (FileDownloadLog.NEED_LOG) {\n            FileDownloadLog.v(this, \"setListener %s\", eventId);\n        }\n        if (listener == null) throw new IllegalArgumentException(\"listener must not be null!\");\n\n        LinkedList<IDownloadListener> container = listenersMap.get(eventId);\n\n        if (container == null) {\n            synchronized (eventId.intern()) {\n                container = listenersMap.get(eventId);\n                if (container == null) {\n                    listenersMap.put(eventId, container = new LinkedList<>());\n                }\n            }\n        }\n\n\n        synchronized (eventId.intern()) {\n            return container.add(listener);\n        }\n    }\n\n    @Override\n    public boolean removeListener(final String eventId, final IDownloadListener listener) {\n        if (FileDownloadLog.NEED_LOG) {\n            FileDownloadLog.v(this, \"removeListener %s\", eventId);\n        }\n\n        LinkedList<IDownloadListener> container = listenersMap.get(eventId);\n        if (container == null) {\n            synchronized (eventId.intern()) {\n                container = listenersMap.get(eventId);\n            }\n        }\n\n        if (container == null || listener == null) {\n            return false;\n        }\n\n        synchronized (eventId.intern()) {\n            boolean succeed = container.remove(listener);\n            if (container.size() <= 0) {\n                listenersMap.remove(eventId);\n            }\n            return succeed;\n        }\n    }\n\n    @Override\n    public boolean publish(final IDownloadEvent event) {\n        if (FileDownloadLog.NEED_LOG) {\n            FileDownloadLog.v(this, \"publish %s\", event.getId());\n        }\n        if (event == null) throw new IllegalArgumentException(\"event must not be null!\");\n        String eventId = event.getId();\n        LinkedList<IDownloadListener> listeners = listenersMap.get(eventId);\n        if (listeners == null) {\n            synchronized (eventId.intern()) {\n                listeners = listenersMap.get(eventId);\n                if (listeners == null) {\n                    if (FileDownloadLog.NEED_LOG) {\n                        FileDownloadLog.d(this, \"No listener for this event %s\", eventId);\n                    }\n                    return false;\n                }\n            }\n        }\n\n        trigger(listeners, event);\n        return true;\n    }\n\n    @Override\n    public void asyncPublishInNewThread(final IDownloadEvent event) {\n        if (FileDownloadLog.NEED_LOG) {\n            FileDownloadLog.v(this, \"asyncPublishInNewThread %s\", event.getId());\n        }\n        if (event == null) throw new IllegalArgumentException(\"event must not be null!\");\n\n        threadPool.execute(new Runnable() {\n            @Override\n            public void run() {\n                DownloadEventPoolImpl.this.publish(event);\n            }\n        });\n    }\n\n    private void trigger(final LinkedList<IDownloadListener> listeners,\n                         final IDownloadEvent event) {\n\n        final Object[] lists = listeners.toArray();\n        for (Object o : lists) {\n            if (o == null) continue; // it has been removed while before listeners.toArray().\n\n            if (((IDownloadListener) o).callback(event)) {\n                break;\n            }\n        }\n\n        if (event.callback != null) {\n            event.callback.run();\n        }\n    }\n}\n"
  },
  {
    "path": "library/src/main/java/com/liulishuo/filedownloader/event/DownloadEventSampleListener.java",
    "content": "/*\n * Copyright (c) 2015 LingoChamp Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *    http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.liulishuo.filedownloader.event;\n\n/**\n * Simplify the listener architecture.\n */\npublic class DownloadEventSampleListener extends IDownloadListener {\n    private final IEventListener i;\n\n    public DownloadEventSampleListener(IEventListener i) {\n        this.i = i;\n    }\n\n//    public DownloadEventSampleListener(int priority, IEventListener i) {\n//        super(priority);\n//        this.i = i;\n//    }\n\n    @Override\n    public boolean callback(IDownloadEvent event) {\n        return i != null && i.callback(event);\n    }\n\n    public interface IEventListener {\n        boolean callback(IDownloadEvent event);\n    }\n\n}\n"
  },
  {
    "path": "library/src/main/java/com/liulishuo/filedownloader/event/DownloadServiceConnectChangedEvent.java",
    "content": "/*\n * Copyright (c) 2015 LingoChamp Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *    http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.liulishuo.filedownloader.event;\n\n\n/**\n * Used to drive the FileDownload Service connection event.\n */\npublic class DownloadServiceConnectChangedEvent extends IDownloadEvent {\n    public static final String ID = \"event.service.connect.changed\";\n\n    public DownloadServiceConnectChangedEvent(final ConnectStatus status,\n                                              final Class<?> serviceClass) {\n        super(ID);\n\n        this.status = status;\n        this.serviceClass = serviceClass;\n    }\n\n    private final ConnectStatus status;\n\n    public enum ConnectStatus {\n        connected, disconnected,\n        // the process hosting the service has crashed or been killed. (do not be unbound manually)\n        lost\n    }\n\n    public ConnectStatus getStatus() {\n        return status;\n    }\n\n    private final Class<?> serviceClass;\n\n    public boolean isSuchService(final Class<?> serviceClass) {\n        return this.serviceClass != null\n                && this.serviceClass.getName().equals(serviceClass.getName());\n\n    }\n}\n"
  },
  {
    "path": "library/src/main/java/com/liulishuo/filedownloader/event/IDownloadEvent.java",
    "content": "/*\n * Copyright (c) 2015 LingoChamp Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *    http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.liulishuo.filedownloader.event;\n\nimport com.liulishuo.filedownloader.util.FileDownloadLog;\n\n/**\n * An atom event.\n */\n@SuppressWarnings({\"WeakerAccess\", \"CanBeFinal\"})\npublic abstract class IDownloadEvent {\n    public Runnable callback = null;\n\n    public IDownloadEvent(final String id) {\n        this.id = id;\n    }\n\n    /**\n     * @see #IDownloadEvent(String)\n     * @deprecated do not handle ORDER any more.\n     */\n    public IDownloadEvent(final String id, boolean order) {\n        this.id = id;\n        if (order) {\n            FileDownloadLog.w(this, \"do not handle ORDER any more, %s\", id);\n        }\n    }\n\n    @SuppressWarnings(\"WeakerAccess\")\n    protected final String id;\n\n    public final String getId() {\n        return this.id;\n    }\n}\n"
  },
  {
    "path": "library/src/main/java/com/liulishuo/filedownloader/event/IDownloadEventPool.java",
    "content": "/*\n * Copyright (c) 2015 LingoChamp Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *    http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.liulishuo.filedownloader.event;\n\n/**\n * The event pool to store the event and listener, and drive them.\n *\n * @see IDownloadEvent\n * @see IDownloadListener\n */\ninterface IDownloadEventPool {\n\n    boolean addListener(final String eventId, final IDownloadListener listener);\n\n    boolean removeListener(final String eventId, final IDownloadListener listener);\n\n    boolean publish(final IDownloadEvent event);\n\n    void asyncPublishInNewThread(final IDownloadEvent event);\n}\n"
  },
  {
    "path": "library/src/main/java/com/liulishuo/filedownloader/event/IDownloadListener.java",
    "content": "/*\n * Copyright (c) 2015 LingoChamp Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *    http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.liulishuo.filedownloader.event;\n\n/**\n * The listener is used to listen the publish event from Event Pool.\n *\n * @see IDownloadEvent\n * @see IDownloadEventPool\n */\npublic abstract class IDownloadListener {\n\n//    private final int priority;\n\n//    public IDownloadListener(int priority) {\n//        this.priority = priority;\n//    }\n\n//    public int getPriority() {\n//        return this.priority;\n//    }\n\n    public abstract boolean callback(IDownloadEvent event);\n\n}\n"
  },
  {
    "path": "library/src/main/java/com/liulishuo/filedownloader/exception/FileDownloadGiveUpRetryException.java",
    "content": "/*\n * Copyright (c) 2015 LingoChamp Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *    http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage com.liulishuo.filedownloader.exception;\n\n/**\n * Throwing this exception, when we can't know the size of the download file, and its\n * Transfer-Encoding is not Chunked either.\n * <p/>\n * When you occur this type exception, the chance of retry will be ignored.\n */\npublic class FileDownloadGiveUpRetryException extends RuntimeException {\n    public FileDownloadGiveUpRetryException(final String detailMessage) {\n        super(detailMessage);\n    }\n}"
  },
  {
    "path": "library/src/main/java/com/liulishuo/filedownloader/exception/FileDownloadHttpException.java",
    "content": "/*\n * Copyright (c) 2015 LingoChamp Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *    http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage com.liulishuo.filedownloader.exception;\n\nimport com.liulishuo.filedownloader.util.FileDownloadUtils;\n\nimport java.io.IOException;\nimport java.util.ArrayList;\nimport java.util.HashMap;\nimport java.util.List;\nimport java.util.Map;\n\n/**\n * Throw this exception, when the HTTP status code is not {@link java.net.HttpURLConnection#HTTP_OK}\n * and not {@link java.net.HttpURLConnection#HTTP_PARTIAL} either.\n */\npublic class FileDownloadHttpException extends IOException {\n    private final int mCode;\n    private final Map<String, List<String>> mRequestHeaderMap;\n    private final Map<String, List<String>> mResponseHeaderMap;\n\n    public FileDownloadHttpException(final int code,\n                                     final Map<String, List<String>> requestHeaderMap,\n                                     final Map<String, List<String>> responseHeaderMap) {\n        super(FileDownloadUtils.formatString(\"response code error: %d, \\n request headers: %s \\n \"\n                + \"response headers: %s\", code, requestHeaderMap, responseHeaderMap));\n\n        this.mCode = code;\n        this.mRequestHeaderMap = cloneSerializableMap(requestHeaderMap);\n        this.mResponseHeaderMap = cloneSerializableMap(requestHeaderMap);\n    }\n\n    /**\n     * @return the header of the current response.\n     */\n    public Map<String, List<String>> getRequestHeader() {\n        return this.mRequestHeaderMap;\n    }\n\n    /**\n     * @return the header of the current request.\n     */\n    public Map<String, List<String>> getResponseHeader() {\n        return this.mResponseHeaderMap;\n    }\n\n    /**\n     * @return the HTTP status code.\n     */\n    public int getCode() {\n        return this.mCode;\n    }\n\n    private static Map<String, List<String>> cloneSerializableMap(\n            final Map<String, List<String>> originMap) {\n        final Map<String, List<String>> serialMap = new HashMap<>();\n\n        for (Map.Entry<String, List<String>> entry : originMap.entrySet()) {\n            final String key = entry.getKey();\n            final List<String> values = new ArrayList<>(entry.getValue());\n            serialMap.put(key, values);\n        }\n\n        return serialMap;\n    }\n}"
  },
  {
    "path": "library/src/main/java/com/liulishuo/filedownloader/exception/FileDownloadNetworkPolicyException.java",
    "content": "/*\n * Copyright (c) 2015 LingoChamp Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *   http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.liulishuo.filedownloader.exception;\n\nimport com.liulishuo.filedownloader.BaseDownloadTask;\n\n/**\n * Throw this exception, If you have set {@code true} to {@link BaseDownloadTask#setWifiRequired}\n * when starting downloading with the network type isn't wifi or in downloading state the network\n * type change to non-Wifi type.\n */\n\npublic class FileDownloadNetworkPolicyException extends FileDownloadGiveUpRetryException {\n    public FileDownloadNetworkPolicyException() {\n        super(\"Only allows downloading this task on the wifi network type\");\n    }\n}"
  },
  {
    "path": "library/src/main/java/com/liulishuo/filedownloader/exception/FileDownloadOutOfSpaceException.java",
    "content": "/*\n * Copyright (c) 2015 LingoChamp Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *    http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage com.liulishuo.filedownloader.exception;\n\nimport android.annotation.TargetApi;\nimport android.os.Build;\n\nimport com.liulishuo.filedownloader.download.DownloadStatusCallback;\nimport com.liulishuo.filedownloader.util.FileDownloadUtils;\n\nimport java.io.IOException;\n\n/**\n * Throw this exception, when the downloading file is too large to store, in other words,\n * the free space is less than the length of the downloading file.\n * <p/>\n * When the resource is non-Chunked(normally), we will check the space and handle this problem\n * before fetch data from the input stream:\n * {@link FileDownloadUtils#createOutputStream(String)}\n * When the resource is chunked, we will handle this problem when the free space is not enough to\n * store the following chunk:\n * {@link DownloadStatusCallback#exFiltrate(Exception)}\n */\n@SuppressWarnings(\"SameParameterValue\")\npublic class FileDownloadOutOfSpaceException extends IOException {\n\n    private long freeSpaceBytes, requiredSpaceBytes, breakpointBytes;\n\n    @TargetApi(Build.VERSION_CODES.GINGERBREAD)\n    public FileDownloadOutOfSpaceException(long freeSpaceBytes, long requiredSpaceBytes,\n                                           long breakpointBytes, Throwable cause) {\n        super(FileDownloadUtils.formatString(\"The file is too large to store, breakpoint in bytes: \"\n                + \" %d, required space in bytes: %d, but free space in bytes: \"\n                + \"%d\", breakpointBytes, requiredSpaceBytes, freeSpaceBytes), cause);\n\n        init(freeSpaceBytes, requiredSpaceBytes, breakpointBytes);\n    }\n\n    public FileDownloadOutOfSpaceException(long freeSpaceBytes, long requiredSpaceBytes,\n                                           long breakpointBytes) {\n        super(FileDownloadUtils.formatString(\"The file is too large to store, breakpoint in bytes: \"\n                + \" %d, required space in bytes: %d, but free space in bytes: \"\n                + \"%d\", breakpointBytes, requiredSpaceBytes, freeSpaceBytes));\n\n        init(freeSpaceBytes, requiredSpaceBytes, breakpointBytes);\n\n    }\n\n    private void init(long freeSpaceBytes, long requiredSpaceBytes, long breakpointBytes) {\n        this.freeSpaceBytes = freeSpaceBytes;\n        this.requiredSpaceBytes = requiredSpaceBytes;\n        this.breakpointBytes = breakpointBytes;\n    }\n\n    /**\n     * @return The free space in bytes.\n     */\n    public long getFreeSpaceBytes() {\n        return freeSpaceBytes;\n    }\n\n    /**\n     * @return The required space in bytes use to store the datum will be fetched.\n     */\n    public long getRequiredSpaceBytes() {\n        return requiredSpaceBytes;\n    }\n\n    /**\n     * @return In normal Case: The value of breakpoint, which has already downloaded by past, if the\n     * value is more than 0, it must be resuming from breakpoint.\n     * For Chunked Resource(Streaming media):\n     * The value would be the filled size.\n     */\n    public long getBreakpointBytes() {\n        return breakpointBytes;\n    }\n}\n"
  },
  {
    "path": "library/src/main/java/com/liulishuo/filedownloader/exception/FileDownloadSecurityException.java",
    "content": "/*\n * Copyright (c) 2015 LingoChamp Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *   http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.liulishuo.filedownloader.exception;\n\n/**\n * Throwing this exception, when there are some security issues found on FileDownloader.\n */\npublic class FileDownloadSecurityException extends Exception {\n    public FileDownloadSecurityException(String msg) {\n        super(msg);\n    }\n}\n"
  },
  {
    "path": "library/src/main/java/com/liulishuo/filedownloader/exception/PathConflictException.java",
    "content": "/*\n * Copyright (c) 2015 LingoChamp Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *   http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.liulishuo.filedownloader.exception;\n\nimport com.liulishuo.filedownloader.util.FileDownloadUtils;\n\n/**\n * Throw this exception, when there is an another running task with the same path with the current\n * task, so if the current task is started, the path of the file is sure to be written by multiple\n * tasks, it is wrong, then we through this exception to avoid such conflict.\n */\n\npublic class PathConflictException extends IllegalAccessException {\n\n    private final String mDownloadingConflictPath;\n    private final String mTargetFilePath;\n    private final int mAnotherSamePathTaskId;\n\n    public PathConflictException(final int anotherSamePathTaskId, final String conflictPath,\n                                 final String targetFilePath) {\n        super(FileDownloadUtils.formatString(\"There is an another running task(%d) with the\"\n                        + \" same downloading path(%s), because of they are with the same \"\n                        + \"target-file-path(%s), so if the current task is started, the path of the\"\n                        + \" file is sure to be written by multiple tasks, it is wrong, then you \"\n                        + \"receive this exception to avoid such conflict.\",\n                anotherSamePathTaskId, conflictPath, targetFilePath));\n\n        mAnotherSamePathTaskId = anotherSamePathTaskId;\n        mDownloadingConflictPath = conflictPath;\n        mTargetFilePath = targetFilePath;\n    }\n\n    /**\n     * Get the conflict downloading file path, normally, this path is used for store the downloading\n     * file relate with the {@link #mTargetFilePath}, and it would be generated from\n     * {@link FileDownloadUtils#getTempPath(String)}.\n     *\n     * @return the conflict downloading file path.\n     */\n    public String getDownloadingConflictPath() {\n        return mDownloadingConflictPath;\n    }\n\n    /**\n     * Get the target file path, which downloading file path is conflict when downloading the task.\n     *\n     * @return the target file path, which downloading file path is conflict when downloading the\n     * task.\n     */\n    public String getTargetFilePath() {\n        return mTargetFilePath;\n    }\n\n    /**\n     * Get the identify of another task which has the same path with the current task and its target\n     * file path is the same to the current task too.\n     *\n     * @return the identify of another task which has the same path with the current task and its\n     * target file path is the same to the current task too.\n     */\n    public int getAnotherSamePathTaskId() {\n        return mAnotherSamePathTaskId;\n    }\n}\n"
  },
  {
    "path": "library/src/main/java/com/liulishuo/filedownloader/message/BlockCompleteMessage.java",
    "content": "/*\n * Copyright (c) 2015 LingoChamp Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *   http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.liulishuo.filedownloader.message;\n\nimport com.liulishuo.filedownloader.model.FileDownloadStatus;\nimport com.liulishuo.filedownloader.util.FileDownloadUtils;\n\n/**\n * The interface of block complete message.\n *\n * @see SmallMessageSnapshot\n * @see LargeMessageSnapshot\n */\n\npublic interface BlockCompleteMessage {\n\n    MessageSnapshot transmitToCompleted();\n\n    class BlockCompleteMessageImpl extends MessageSnapshot implements BlockCompleteMessage {\n        private final MessageSnapshot mCompletedSnapshot;\n\n        public BlockCompleteMessageImpl(MessageSnapshot snapshot) {\n            super(snapshot.getId());\n            if (snapshot.getStatus() != FileDownloadStatus.completed) {\n                throw new IllegalArgumentException(FileDownloadUtils.formatString(\n                        \"can't create the block complete message for id[%d], status[%d]\",\n                        snapshot.getId(), snapshot.getStatus()));\n            }\n            this.mCompletedSnapshot = snapshot;\n        }\n\n        @Override\n        public MessageSnapshot transmitToCompleted() {\n            return this.mCompletedSnapshot;\n        }\n\n        @Override\n        public byte getStatus() {\n            return FileDownloadStatus.blockComplete;\n        }\n    }\n\n}\n"
  },
  {
    "path": "library/src/main/java/com/liulishuo/filedownloader/message/IFlowDirectly.java",
    "content": "/*\n * Copyright (c) 2015 LingoChamp Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *    http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.liulishuo.filedownloader.message;\n\n/**\n * If the snapshot implement this interface, it will be flowed directly, it means that it would be\n * callback to the message station synchronize, not through the keep-flow-thread-pool.\n */\npublic interface IFlowDirectly {\n}\n"
  },
  {
    "path": "library/src/main/java/com/liulishuo/filedownloader/message/IMessageSnapshot.java",
    "content": "/*\n * Copyright (c) 2015 LingoChamp Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *    http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.liulishuo.filedownloader.message;\n\n/**\n * The snapshot interface.\n */\ninterface IMessageSnapshot {\n    /**\n     * @return The download identify.\n     */\n    int getId();\n\n    /**\n     * @return The current downloading status.\n     * @see com.liulishuo.filedownloader.model.FileDownloadStatus\n     */\n    byte getStatus();\n\n    /**\n     * @return The error cause.\n     */\n    Throwable getThrowable();\n\n    /**\n     * @return The currently retrying times.\n     */\n    int getRetryingTimes();\n\n    /**\n     * @return {@code true} if the downloading is resuming from the breakpoint, otherwise the\n     * downloading is from the beginning.\n     */\n    boolean isResuming();\n\n    /**\n     * @return the Etag from the response's header.\n     */\n    String getEtag();\n\n    /**\n     * This method will be used when the downloading file is a large file.\n     *\n     * @return The so far downloaded bytes.\n     * @see #isLargeFile()\n     */\n    long getLargeSofarBytes();\n\n    /**\n     * This method will be used when the downloading file is a large file.\n     *\n     * @return The total bytes of the downloading file.\n     * @see #isLargeFile()\n     */\n    long getLargeTotalBytes();\n\n    /**\n     * This method will be used when the downloading file isn't a large file.\n     *\n     * @return The so far downloaded bytes.\n     * @see #isLargeFile()\n     */\n    int getSmallSofarBytes();\n\n    /**\n     * This method will be used when the downloading file isn't a large file.\n     *\n     * @return The total bytes of the downloading file.\n     * @see #isLargeFile()\n     */\n    int getSmallTotalBytes();\n\n    /**\n     * @return {@code true} if the task isn't real started, and we find the target file is already\n     * exist, so the task will receive the completed callback directly, {@code false} otherwise.\n     */\n    boolean isReusedDownloadedFile();\n\n    /**\n     * @return {@code true} if the length of the file is more than 1.99G, {@code false} otherwise.\n     */\n    boolean isLargeFile();\n\n    /**\n     * @return The filename.\n     */\n    String getFileName();\n}"
  },
  {
    "path": "library/src/main/java/com/liulishuo/filedownloader/message/LargeMessageSnapshot.java",
    "content": "/*\n * Copyright (c) 2015 LingoChamp Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *    http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.liulishuo.filedownloader.message;\n\nimport android.os.Parcel;\n\nimport com.liulishuo.filedownloader.model.FileDownloadStatus;\n\n/**\n * A message snapshot for large file(the length is more than or equal to 2G).\n *\n * @see SmallMessageSnapshot\n * @see BlockCompleteMessage\n */\npublic abstract class LargeMessageSnapshot extends MessageSnapshot {\n\n    LargeMessageSnapshot(int id) {\n        super(id);\n        isLargeFile = true;\n    }\n\n    LargeMessageSnapshot(Parcel in) {\n        super(in);\n    }\n\n    @Override\n    public int getSmallSofarBytes() {\n        if (getLargeSofarBytes() > Integer.MAX_VALUE) {\n            return Integer.MAX_VALUE;\n        }\n\n        return (int) getLargeSofarBytes();\n    }\n\n    @Override\n    public int getSmallTotalBytes() {\n        if (getLargeTotalBytes() > Integer.MAX_VALUE) {\n            return Integer.MAX_VALUE;\n        }\n\n        return (int) getLargeTotalBytes();\n    }\n\n    // Pending Snapshot\n    public static class PendingMessageSnapshot extends LargeMessageSnapshot {\n\n        private final long sofarBytes, totalBytes;\n\n        PendingMessageSnapshot(PendingMessageSnapshot snapshot) {\n            this(snapshot.getId(), snapshot.getLargeSofarBytes(), snapshot.getLargeTotalBytes());\n        }\n\n        PendingMessageSnapshot(int id, long sofarBytes, long totalBytes) {\n            super(id);\n            this.sofarBytes = sofarBytes;\n            this.totalBytes = totalBytes;\n        }\n\n        @Override\n        public byte getStatus() {\n            return FileDownloadStatus.pending;\n        }\n\n        @Override\n        public long getLargeSofarBytes() {\n            return sofarBytes;\n        }\n\n        @Override\n        public long getLargeTotalBytes() {\n            return totalBytes;\n        }\n\n        @Override\n        public int describeContents() {\n            return 0;\n        }\n\n        @Override\n        public void writeToParcel(Parcel dest, int flags) {\n            super.writeToParcel(dest, flags);\n            dest.writeLong(this.sofarBytes);\n            dest.writeLong(this.totalBytes);\n        }\n\n        PendingMessageSnapshot(Parcel in) {\n            super(in);\n            this.sofarBytes = in.readLong();\n            this.totalBytes = in.readLong();\n        }\n    }\n\n    // Connected Snapshot\n    public static class ConnectedMessageSnapshot extends LargeMessageSnapshot {\n        private final boolean resuming;\n        private final long totalBytes;\n        private final String etag;\n        private final String fileName;\n\n        ConnectedMessageSnapshot(int id, boolean resuming, long totalBytes,\n                                 String etag, String fileName) {\n            super(id);\n            this.resuming = resuming;\n            this.totalBytes = totalBytes;\n            this.etag = etag;\n            this.fileName = fileName;\n        }\n\n        @Override\n        public int describeContents() {\n            return 0;\n        }\n\n        @Override\n        public void writeToParcel(Parcel dest, int flags) {\n            super.writeToParcel(dest, flags);\n            dest.writeByte(resuming ? (byte) 1 : (byte) 0);\n            dest.writeLong(this.totalBytes);\n            dest.writeString(this.etag);\n            dest.writeString(this.fileName);\n        }\n\n        ConnectedMessageSnapshot(Parcel in) {\n            super(in);\n            this.resuming = in.readByte() != 0;\n            this.totalBytes = in.readLong();\n            this.etag = in.readString();\n            this.fileName = in.readString();\n        }\n\n        @Override\n        public String getFileName() {\n            return fileName;\n        }\n\n        @Override\n        public byte getStatus() {\n            return FileDownloadStatus.connected;\n        }\n\n        @Override\n        public boolean isResuming() {\n            return resuming;\n        }\n\n        @Override\n        public long getLargeTotalBytes() {\n            return totalBytes;\n        }\n\n        @Override\n        public String getEtag() {\n            return etag;\n        }\n    }\n\n    // Progress Snapshot\n    public static class ProgressMessageSnapshot extends LargeMessageSnapshot {\n        private final long sofarBytes;\n\n        ProgressMessageSnapshot(int id, long sofarBytes) {\n            super(id);\n            this.sofarBytes = sofarBytes;\n        }\n\n        @Override\n        public byte getStatus() {\n            return FileDownloadStatus.progress;\n        }\n\n        @Override\n        public long getLargeSofarBytes() {\n            return sofarBytes;\n        }\n\n        @Override\n        public int describeContents() {\n            return 0;\n        }\n\n        @Override\n        public void writeToParcel(Parcel dest, int flags) {\n            super.writeToParcel(dest, flags);\n            dest.writeLong(this.sofarBytes);\n        }\n\n        ProgressMessageSnapshot(Parcel in) {\n            super(in);\n            this.sofarBytes = in.readLong();\n        }\n    }\n\n    // Completed Snapshot\n    public static class CompletedFlowDirectlySnapshot extends CompletedSnapshot implements\n            IFlowDirectly {\n\n        CompletedFlowDirectlySnapshot(int id, boolean reusedDownloadedFile,\n                                      long totalBytes) {\n            super(id, reusedDownloadedFile, totalBytes);\n        }\n\n        CompletedFlowDirectlySnapshot(Parcel in) {\n            super(in);\n        }\n    }\n\n    public static class CompletedSnapshot extends LargeMessageSnapshot {\n        private final boolean reusedDownloadedFile;\n        private final long totalBytes;\n\n        CompletedSnapshot(int id, boolean reusedDownloadedFile,\n                          long totalBytes) {\n            super(id);\n            this.reusedDownloadedFile = reusedDownloadedFile;\n            this.totalBytes = totalBytes;\n        }\n\n        @Override\n        public int describeContents() {\n            return 0;\n        }\n\n        @Override\n        public void writeToParcel(Parcel dest, int flags) {\n            super.writeToParcel(dest, flags);\n            dest.writeByte(reusedDownloadedFile ? (byte) 1 : (byte) 0);\n            dest.writeLong(this.totalBytes);\n        }\n\n        CompletedSnapshot(Parcel in) {\n            super(in);\n            this.reusedDownloadedFile = in.readByte() != 0;\n            this.totalBytes = in.readLong();\n        }\n\n        @Override\n        public byte getStatus() {\n            return FileDownloadStatus.completed;\n        }\n\n        @Override\n        public long getLargeTotalBytes() {\n            return totalBytes;\n        }\n\n        @Override\n        public boolean isReusedDownloadedFile() {\n            return reusedDownloadedFile;\n        }\n    }\n\n    // Error Snapshot\n    public static class ErrorMessageSnapshot extends LargeMessageSnapshot {\n        private final long sofarBytes;\n        private final Throwable throwable;\n\n        ErrorMessageSnapshot(int id, long sofarBytes, Throwable throwable) {\n            super(id);\n            this.sofarBytes = sofarBytes;\n            this.throwable = throwable;\n        }\n\n        @Override\n        public long getLargeSofarBytes() {\n            return sofarBytes;\n        }\n\n        @Override\n        public byte getStatus() {\n            return FileDownloadStatus.error;\n        }\n\n        @Override\n        public Throwable getThrowable() {\n            return throwable;\n        }\n\n        @Override\n        public int describeContents() {\n            return 0;\n        }\n\n        @Override\n        public void writeToParcel(Parcel dest, int flags) {\n            super.writeToParcel(dest, flags);\n            dest.writeLong(this.sofarBytes);\n            dest.writeSerializable(this.throwable);\n        }\n\n        ErrorMessageSnapshot(Parcel in) {\n            super(in);\n            this.sofarBytes = in.readLong();\n            this.throwable = (Throwable) in.readSerializable();\n        }\n    }\n\n    // Retry Snapshot\n    public static class RetryMessageSnapshot extends ErrorMessageSnapshot {\n        private final int retryingTimes;\n\n        RetryMessageSnapshot(int id, long sofarBytes, Throwable throwable,\n                             int retryingTimes) {\n            super(id, sofarBytes, throwable);\n            this.retryingTimes = retryingTimes;\n        }\n\n        @Override\n        public int getRetryingTimes() {\n            return retryingTimes;\n        }\n\n        @Override\n        public int describeContents() {\n            return 0;\n        }\n\n        @Override\n        public void writeToParcel(Parcel dest, int flags) {\n            super.writeToParcel(dest, flags);\n            dest.writeInt(this.retryingTimes);\n        }\n\n        RetryMessageSnapshot(Parcel in) {\n            super(in);\n            this.retryingTimes = in.readInt();\n        }\n\n        @Override\n        public byte getStatus() {\n            return FileDownloadStatus.retry;\n        }\n    }\n\n    // Warn Snapshot\n    public static class WarnFlowDirectlySnapshot extends WarnMessageSnapshot implements\n            IFlowDirectly {\n\n        WarnFlowDirectlySnapshot(int id, long sofarBytes, long totalBytes) {\n            super(id, sofarBytes, totalBytes);\n        }\n\n        WarnFlowDirectlySnapshot(Parcel in) {\n            super(in);\n        }\n    }\n\n    public static class WarnMessageSnapshot extends PendingMessageSnapshot implements\n            IWarnMessageSnapshot {\n\n        WarnMessageSnapshot(int id, long sofarBytes, long totalBytes) {\n            super(id, sofarBytes, totalBytes);\n        }\n\n        WarnMessageSnapshot(Parcel in) {\n            super(in);\n        }\n\n        @Override\n        public MessageSnapshot turnToPending() {\n            return new LargeMessageSnapshot.PendingMessageSnapshot(this);\n        }\n\n        @Override\n        public byte getStatus() {\n            return FileDownloadStatus.warn;\n        }\n    }\n\n    // Paused Snapshot\n    public static class PausedSnapshot extends PendingMessageSnapshot {\n        PausedSnapshot(int id, long sofarBytes, long totalBytes) {\n            super(id, sofarBytes, totalBytes);\n        }\n\n        @Override\n        public byte getStatus() {\n            return FileDownloadStatus.paused;\n        }\n    }\n}\n"
  },
  {
    "path": "library/src/main/java/com/liulishuo/filedownloader/message/MessageSnapshot.java",
    "content": "/*\n * Copyright (c) 2015 LingoChamp Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *    http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.liulishuo.filedownloader.message;\n\nimport android.os.Parcel;\nimport android.os.Parcelable;\n\nimport com.liulishuo.filedownloader.model.FileDownloadStatus;\nimport com.liulishuo.filedownloader.util.FileDownloadUtils;\n\n/**\n * The message snapshot.\n */\npublic abstract class MessageSnapshot implements IMessageSnapshot, Parcelable {\n    private final int id;\n    protected boolean isLargeFile;\n\n    MessageSnapshot(int id) {\n        this.id = id;\n    }\n\n    @Override\n    public int getId() {\n        return id;\n    }\n\n    @Override\n    public Throwable getThrowable() {\n        throw new NoFieldException(\"getThrowable\", this);\n    }\n\n    @Override\n    public int getRetryingTimes() {\n        throw new NoFieldException(\"getRetryingTimes\", this);\n    }\n\n    @Override\n    public boolean isResuming() {\n        throw new NoFieldException(\"isResuming\", this);\n    }\n\n    @Override\n    public String getEtag() {\n        throw new NoFieldException(\"getEtag\", this);\n    }\n\n    @Override\n    public long getLargeSofarBytes() {\n        throw new NoFieldException(\"getLargeSofarBytes\", this);\n    }\n\n    @Override\n    public long getLargeTotalBytes() {\n        throw new NoFieldException(\"getLargeTotalBytes\", this);\n    }\n\n    @Override\n    public int getSmallSofarBytes() {\n        throw new NoFieldException(\"getSmallSofarBytes\", this);\n    }\n\n    @Override\n    public int getSmallTotalBytes() {\n        throw new NoFieldException(\"getSmallTotalBytes\", this);\n    }\n\n    @Override\n    public boolean isReusedDownloadedFile() {\n        throw new NoFieldException(\"isReusedDownloadedFile\", this);\n    }\n\n    @Override\n    public String getFileName() {\n        throw new NoFieldException(\"getFileName\", this);\n    }\n\n    @Override\n    public boolean isLargeFile() {\n        return isLargeFile;\n    }\n\n\n    public interface IWarnMessageSnapshot {\n        MessageSnapshot turnToPending();\n    }\n\n\n    @Override\n    public int describeContents() {\n        return 0;\n    }\n\n    @Override\n    public void writeToParcel(Parcel dest, int flags) {\n        dest.writeByte((byte) (isLargeFile ? 1 : 0));\n        dest.writeByte(getStatus());\n        // normal\n        dest.writeInt(this.id);\n    }\n\n    MessageSnapshot(Parcel in) {\n        this.id = in.readInt();\n    }\n\n    public static final Creator<MessageSnapshot> CREATOR = new Creator<MessageSnapshot>() {\n        @Override\n        public MessageSnapshot createFromParcel(Parcel source) {\n            boolean largeFile = source.readByte() == 1;\n            byte status = source.readByte();\n            final MessageSnapshot snapshot;\n            switch (status) {\n                case FileDownloadStatus.pending:\n                    if (largeFile) {\n                        snapshot = new LargeMessageSnapshot.PendingMessageSnapshot(source);\n                    } else {\n                        snapshot = new SmallMessageSnapshot.PendingMessageSnapshot(source);\n                    }\n                    break;\n                case FileDownloadStatus.started:\n                    snapshot = new StartedMessageSnapshot(source);\n                    break;\n                case FileDownloadStatus.connected:\n                    if (largeFile) {\n                        snapshot = new LargeMessageSnapshot.ConnectedMessageSnapshot(source);\n                    } else {\n                        snapshot = new SmallMessageSnapshot.ConnectedMessageSnapshot(source);\n                    }\n                    break;\n                case FileDownloadStatus.progress:\n                    if (largeFile) {\n                        snapshot = new LargeMessageSnapshot.ProgressMessageSnapshot(source);\n                    } else {\n                        snapshot = new SmallMessageSnapshot.ProgressMessageSnapshot(source);\n                    }\n                    break;\n                case FileDownloadStatus.retry:\n                    if (largeFile) {\n                        snapshot = new LargeMessageSnapshot.RetryMessageSnapshot(source);\n                    } else {\n                        snapshot = new SmallMessageSnapshot.RetryMessageSnapshot(source);\n                    }\n                    break;\n                case FileDownloadStatus.error:\n                    if (largeFile) {\n                        snapshot = new LargeMessageSnapshot.ErrorMessageSnapshot(source);\n                    } else {\n                        snapshot = new SmallMessageSnapshot.ErrorMessageSnapshot(source);\n                    }\n                    break;\n                case FileDownloadStatus.completed:\n                    if (largeFile) {\n                        snapshot = new LargeMessageSnapshot.CompletedSnapshot(source);\n                    } else {\n                        snapshot = new SmallMessageSnapshot.CompletedSnapshot(source);\n                    }\n                    break;\n                case FileDownloadStatus.warn:\n                    if (largeFile) {\n                        snapshot = new LargeMessageSnapshot.WarnMessageSnapshot(source);\n                    } else {\n                        snapshot = new SmallMessageSnapshot.WarnMessageSnapshot(source);\n                    }\n                    break;\n                default:\n                    snapshot = null;\n            }\n\n            if (snapshot != null) {\n                snapshot.isLargeFile = largeFile;\n            } else {\n                throw new IllegalStateException(\"Can't restore the snapshot because unknown \"\n                        + \"status: \" + status);\n            }\n\n            return snapshot;\n        }\n\n        @Override\n        public MessageSnapshot[] newArray(int size) {\n            return new MessageSnapshot[size];\n        }\n    };\n\n    public static class NoFieldException extends IllegalStateException {\n        NoFieldException(String methodName, MessageSnapshot snapshot) {\n            super(FileDownloadUtils.formatString(\"There isn't a field for '%s' in this message\"\n                            + \" %d %d %s\",\n                    methodName, snapshot.getId(), snapshot.getStatus(),\n                    snapshot.getClass().getName()));\n        }\n    }\n\n    // Started Snapshot\n    public static class StartedMessageSnapshot extends MessageSnapshot {\n\n        StartedMessageSnapshot(int id) {\n            super(id);\n        }\n\n        StartedMessageSnapshot(Parcel in) {\n            super(in);\n        }\n\n        @Override\n        public byte getStatus() {\n            return FileDownloadStatus.started;\n        }\n    }\n}\n"
  },
  {
    "path": "library/src/main/java/com/liulishuo/filedownloader/message/MessageSnapshotFlow.java",
    "content": "/*\n * Copyright (c) 2015 LingoChamp Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *    http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.liulishuo.filedownloader.message;\n\n/**\n * The internal message snapshot station.\n * <p>\n * Making message snapshots keep flowing in order.\n */\npublic class MessageSnapshotFlow {\n\n    private volatile MessageSnapshotThreadPool flowThreadPool;\n    private volatile MessageReceiver receiver;\n\n    public static final class HolderClass {\n        private static final MessageSnapshotFlow INSTANCE = new MessageSnapshotFlow();\n    }\n\n    public static MessageSnapshotFlow getImpl() {\n        return HolderClass.INSTANCE;\n    }\n\n    public void setReceiver(MessageReceiver receiver) {\n        this.receiver = receiver;\n        if (receiver == null) {\n            this.flowThreadPool = null;\n        } else {\n            this.flowThreadPool = new MessageSnapshotThreadPool(5, receiver);\n        }\n    }\n\n    public void inflow(final MessageSnapshot snapshot) {\n        if (snapshot instanceof IFlowDirectly) {\n            if (receiver != null) {\n                receiver.receive(snapshot);\n            }\n        } else {\n            if (flowThreadPool != null) {\n                flowThreadPool.execute(snapshot);\n            }\n        }\n\n    }\n\n\n    public interface MessageReceiver {\n        void receive(MessageSnapshot snapshot);\n    }\n}\n"
  },
  {
    "path": "library/src/main/java/com/liulishuo/filedownloader/message/MessageSnapshotTaker.java",
    "content": "/*\n * Copyright (c) 2015 LingoChamp Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *    http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.liulishuo.filedownloader.message;\n\nimport com.liulishuo.filedownloader.BaseDownloadTask;\nimport com.liulishuo.filedownloader.download.DownloadStatusCallback;\nimport com.liulishuo.filedownloader.model.FileDownloadModel;\nimport com.liulishuo.filedownloader.model.FileDownloadStatus;\nimport com.liulishuo.filedownloader.util.FileDownloadLog;\nimport com.liulishuo.filedownloader.util.FileDownloadUtils;\n\nimport java.io.File;\n\n/**\n * The factory for taking message snapshots.\n */\npublic class MessageSnapshotTaker {\n\n    public static MessageSnapshot take(byte status, FileDownloadModel model) {\n        return take(status, model, null);\n    }\n\n    public static MessageSnapshot catchCanReusedOldFile(int id, File oldFile,\n                                                        boolean flowDirectly) {\n        final long totalBytes = oldFile.length();\n        if (totalBytes > Integer.MAX_VALUE) {\n            if (flowDirectly) {\n                return new LargeMessageSnapshot.CompletedFlowDirectlySnapshot(id, true, totalBytes);\n            } else {\n                return new LargeMessageSnapshot.CompletedSnapshot(id, true, totalBytes);\n            }\n        } else {\n            if (flowDirectly) {\n                return new SmallMessageSnapshot.CompletedFlowDirectlySnapshot(id, true,\n                        (int) totalBytes);\n            } else {\n                return new SmallMessageSnapshot.CompletedSnapshot(id, true, (int) totalBytes);\n            }\n        }\n    }\n\n    public static MessageSnapshot catchWarn(int id, long sofar, long total, boolean flowDirectly) {\n        if (total > Integer.MAX_VALUE) {\n            if (flowDirectly) {\n                return new LargeMessageSnapshot.WarnFlowDirectlySnapshot(id, sofar, total);\n            } else {\n                return new LargeMessageSnapshot.WarnMessageSnapshot(id, sofar, total);\n            }\n        } else {\n            if (flowDirectly) {\n                return new SmallMessageSnapshot.WarnFlowDirectlySnapshot(id, (int) sofar,\n                        (int) total);\n            } else {\n                return new SmallMessageSnapshot.WarnMessageSnapshot(id, (int) sofar, (int) total);\n            }\n        }\n    }\n\n    public static MessageSnapshot catchException(int id, long sofar, Throwable error) {\n        if (sofar > Integer.MAX_VALUE) {\n            return new LargeMessageSnapshot.ErrorMessageSnapshot(id, sofar, error);\n        } else {\n            return new SmallMessageSnapshot.ErrorMessageSnapshot(id, (int) sofar, error);\n        }\n    }\n\n    public static MessageSnapshot catchPause(BaseDownloadTask task) {\n        if (task.isLargeFile()) {\n            return new LargeMessageSnapshot.PausedSnapshot(task.getId(),\n                    task.getLargeFileSoFarBytes(), task.getLargeFileTotalBytes());\n        } else {\n            return new SmallMessageSnapshot.PausedSnapshot(task.getId(),\n                    task.getSmallFileSoFarBytes(), task.getSmallFileTotalBytes());\n        }\n    }\n\n    public static MessageSnapshot takeBlockCompleted(MessageSnapshot snapshot) {\n        if (snapshot.getStatus() != FileDownloadStatus.completed) {\n            throw new IllegalStateException(\n                    FileDownloadUtils.formatString(\"take block completed snapshot, must has \"\n                                    + \"already be completed. %d %d\",\n                            snapshot.getId(), snapshot.getStatus()));\n        }\n\n        return new BlockCompleteMessage.BlockCompleteMessageImpl(snapshot);\n    }\n\n    public static MessageSnapshot take(byte status, FileDownloadModel model,\n                                       DownloadStatusCallback.ProcessParams processParams) {\n        final MessageSnapshot snapShot;\n        final int id = model.getId();\n        if (status == FileDownloadStatus.warn) {\n            throw new IllegalStateException(FileDownloadUtils.\n                    formatString(\"please use #catchWarn instead %d\", id));\n        }\n\n        switch (status) {\n            case FileDownloadStatus.pending:\n                if (model.isLargeFile()) {\n                    snapShot = new LargeMessageSnapshot.PendingMessageSnapshot(id,\n                            model.getSoFar(), model.getTotal());\n                } else {\n                    snapShot = new SmallMessageSnapshot.PendingMessageSnapshot(id,\n                            (int) model.getSoFar(), (int) model.getTotal());\n                }\n                break;\n            case FileDownloadStatus.started:\n                snapShot = new MessageSnapshot.StartedMessageSnapshot(id);\n                break;\n            case FileDownloadStatus.connected:\n                final String filename = model.isPathAsDirectory() ? model.getFilename() : null;\n                if (model.isLargeFile()) {\n                    snapShot = new LargeMessageSnapshot.ConnectedMessageSnapshot(id,\n                            processParams.isResuming(), model.getTotal(), model.getETag(),\n                            filename);\n                } else {\n                    snapShot = new SmallMessageSnapshot.ConnectedMessageSnapshot(id,\n                            processParams.isResuming(), (int) model.getTotal(), model.getETag(),\n                            filename);\n                }\n                break;\n            case FileDownloadStatus.progress:\n                if (model.isLargeFile()) {\n                    snapShot = new LargeMessageSnapshot.\n                            ProgressMessageSnapshot(id, model.getSoFar());\n                } else {\n                    snapShot = new SmallMessageSnapshot.\n                            ProgressMessageSnapshot(id, (int) model.getSoFar());\n                }\n                break;\n            case FileDownloadStatus.completed:\n                if (model.isLargeFile()) {\n                    snapShot = new LargeMessageSnapshot.\n                            CompletedSnapshot(id, false, model.getTotal());\n                } else {\n                    snapShot = new SmallMessageSnapshot.\n                            CompletedSnapshot(id, false, (int) model.getTotal());\n                }\n                break;\n            case FileDownloadStatus.retry:\n                if (model.isLargeFile()) {\n                    snapShot = new LargeMessageSnapshot.RetryMessageSnapshot(id,\n                            model.getSoFar(), processParams.getException(),\n                            processParams.getRetryingTimes());\n                } else {\n                    snapShot = new SmallMessageSnapshot.RetryMessageSnapshot(id,\n                            (int) model.getSoFar(), processParams.getException(),\n                            processParams.getRetryingTimes());\n                }\n                break;\n            case FileDownloadStatus.error:\n                if (model.isLargeFile()) {\n                    snapShot = new LargeMessageSnapshot.ErrorMessageSnapshot(id,\n                            model.getSoFar(), processParams.getException());\n                } else {\n                    snapShot = new SmallMessageSnapshot.ErrorMessageSnapshot(id,\n                            (int) model.getSoFar(), processParams.getException());\n                }\n                break;\n            default:\n                // deal with as error.\n                final String message = FileDownloadUtils.\n                        formatString(\n                                \"it can't takes a snapshot for the task(%s) when its status is %d,\",\n                                model, status);\n\n                FileDownloadLog.w(MessageSnapshotTaker.class,\n                        \"it can't takes a snapshot for the task(%s) when its status is %d,\", model,\n                        status);\n\n                final Throwable throwable;\n                if (processParams.getException() != null) {\n                    throwable = new IllegalStateException(message, processParams.getException());\n                } else {\n                    throwable = new IllegalStateException(message);\n                }\n\n                if (model.isLargeFile()) {\n                    snapShot = new LargeMessageSnapshot.ErrorMessageSnapshot(id,\n                            model.getSoFar(), throwable);\n                } else {\n                    snapShot = new SmallMessageSnapshot.ErrorMessageSnapshot(id,\n                            (int) model.getSoFar(), throwable);\n                }\n                break;\n        }\n\n        return snapShot;\n    }\n}\n"
  },
  {
    "path": "library/src/main/java/com/liulishuo/filedownloader/message/MessageSnapshotThreadPool.java",
    "content": "/*\n * Copyright (c) 2015 LingoChamp Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *    http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage com.liulishuo.filedownloader.message;\n\nimport com.liulishuo.filedownloader.util.FileDownloadExecutors;\n\nimport java.util.ArrayList;\nimport java.util.List;\nimport java.util.concurrent.Executor;\n\n/**\n * For guaranteeing only one-thread-pool for one-task, the task will be identified by its ID, make\n * sure the same task will be invoked in FIFO.\n */\npublic class MessageSnapshotThreadPool {\n\n    private final List<FlowSingleExecutor> executorList;\n\n    private final MessageSnapshotFlow.MessageReceiver receiver;\n\n    MessageSnapshotThreadPool(@SuppressWarnings(\"SameParameterValue\") final int poolCount,\n                              MessageSnapshotFlow.MessageReceiver receiver) {\n        this.receiver = receiver;\n        executorList = new ArrayList<>();\n        for (int i = 0; i < poolCount; i++) {\n            executorList.add(new FlowSingleExecutor(i));\n        }\n    }\n\n    public void execute(final MessageSnapshot snapshot) {\n        FlowSingleExecutor targetPool = null;\n        try {\n            synchronized (executorList) {\n                final int id = snapshot.getId();\n                // Case 1. already had same task in executorList, so execute this event after\n                // before-one.\n                for (FlowSingleExecutor executor : executorList) {\n                    if (executor.enQueueTaskIdList.contains(id)) {\n                        targetPool = executor;\n                        break;\n                    }\n                }\n\n                // Case 2. no same task in executorList, so execute in executor which has the count\n                // of active task is least.\n                if (targetPool == null) {\n                    int leastTaskCount = 0;\n                    for (FlowSingleExecutor executor : executorList) {\n                        if (executor.enQueueTaskIdList.size() <= 0) {\n                            targetPool = executor;\n                            break;\n                        }\n\n                        if (leastTaskCount == 0\n                                || executor.enQueueTaskIdList.size() < leastTaskCount) {\n                            leastTaskCount = executor.enQueueTaskIdList.size();\n                            targetPool = executor;\n                        }\n                    }\n                }\n\n                //noinspection ConstantConditions\n                targetPool.enqueue(id);\n            }\n        } finally {\n            //noinspection ConstantConditions\n            targetPool.execute(snapshot);\n        }\n    }\n\n    public class FlowSingleExecutor {\n        private final List<Integer> enQueueTaskIdList = new ArrayList<>();\n        private final Executor mExecutor;\n\n        public FlowSingleExecutor(int index) {\n            mExecutor = FileDownloadExecutors.newDefaultThreadPool(1, \"Flow-\" + index);\n        }\n\n        public void enqueue(final int id) {\n            enQueueTaskIdList.add(id);\n        }\n\n        public void execute(final MessageSnapshot snapshot) {\n            mExecutor.execute(new Runnable() {\n                @Override\n                public void run() {\n                    receiver.receive(snapshot);\n                    enQueueTaskIdList.remove((Integer) snapshot.getId());\n                }\n            });\n        }\n\n    }\n}\n"
  },
  {
    "path": "library/src/main/java/com/liulishuo/filedownloader/message/SmallMessageSnapshot.java",
    "content": "/*\n * Copyright (c) 2015 LingoChamp Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *    http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.liulishuo.filedownloader.message;\n\nimport android.os.Parcel;\n\nimport com.liulishuo.filedownloader.model.FileDownloadStatus;\n\n/**\n * A message snapshot for a small file(the length is less than 2G).\n *\n * @see LargeMessageSnapshot\n * @see BlockCompleteMessage\n */\npublic abstract class SmallMessageSnapshot extends MessageSnapshot {\n\n    SmallMessageSnapshot(int id) {\n        super(id);\n        isLargeFile = false;\n    }\n\n    SmallMessageSnapshot(Parcel in) {\n        super(in);\n    }\n\n    @Override\n    public long getLargeTotalBytes() {\n        return getSmallTotalBytes();\n    }\n\n    @Override\n    public long getLargeSofarBytes() {\n        return getSmallSofarBytes();\n    }\n\n    // Pending Snapshot\n    public static class PendingMessageSnapshot extends SmallMessageSnapshot {\n\n        private final int sofarBytes, totalBytes;\n\n        PendingMessageSnapshot(PendingMessageSnapshot snapshot) {\n            this(snapshot.getId(), snapshot.getSmallSofarBytes(), snapshot.getSmallTotalBytes());\n        }\n\n        PendingMessageSnapshot(int id, int sofarBytes, int totalBytes) {\n            super(id);\n            this.sofarBytes = sofarBytes;\n            this.totalBytes = totalBytes;\n        }\n\n        PendingMessageSnapshot(Parcel in) {\n            super(in);\n            this.sofarBytes = in.readInt();\n            this.totalBytes = in.readInt();\n        }\n\n        @Override\n        public void writeToParcel(Parcel dest, int flags) {\n            super.writeToParcel(dest, flags);\n            dest.writeInt(this.sofarBytes);\n            dest.writeInt(this.totalBytes);\n        }\n\n        @Override\n        public byte getStatus() {\n            return FileDownloadStatus.pending;\n        }\n\n        @Override\n        public int getSmallSofarBytes() {\n            return sofarBytes;\n        }\n\n        @Override\n        public int getSmallTotalBytes() {\n            return totalBytes;\n        }\n    }\n\n\n    // Connected Snapshot\n    public static class ConnectedMessageSnapshot extends SmallMessageSnapshot {\n        private final boolean resuming;\n        private final int totalBytes;\n        private final String etag;\n        private final String fileName;\n\n        ConnectedMessageSnapshot(int id, boolean resuming, int totalBytes,\n                                 String etag, String fileName) {\n            super(id);\n            this.resuming = resuming;\n            this.totalBytes = totalBytes;\n            this.etag = etag;\n            this.fileName = fileName;\n        }\n\n        @Override\n        public int describeContents() {\n            return 0;\n        }\n\n        @Override\n        public void writeToParcel(Parcel dest, int flags) {\n            super.writeToParcel(dest, flags);\n            dest.writeByte(resuming ? (byte) 1 : (byte) 0);\n            dest.writeInt(this.totalBytes);\n            dest.writeString(this.etag);\n            dest.writeString(this.fileName);\n        }\n\n        ConnectedMessageSnapshot(Parcel in) {\n            super(in);\n            this.resuming = in.readByte() != 0;\n            this.totalBytes = in.readInt();\n            this.etag = in.readString();\n            this.fileName = in.readString();\n        }\n\n        @Override\n        public String getFileName() {\n            return fileName;\n        }\n\n        @Override\n        public byte getStatus() {\n            return FileDownloadStatus.connected;\n        }\n\n        @Override\n        public boolean isResuming() {\n            return resuming;\n        }\n\n        @Override\n        public int getSmallTotalBytes() {\n            return totalBytes;\n        }\n\n        @Override\n        public String getEtag() {\n            return etag;\n        }\n    }\n\n    // Progress Snapshot\n    public static class ProgressMessageSnapshot extends SmallMessageSnapshot {\n        private final int sofarBytes;\n\n        ProgressMessageSnapshot(int id, int sofarBytes) {\n            super(id);\n            this.sofarBytes = sofarBytes;\n        }\n\n        @Override\n        public byte getStatus() {\n            return FileDownloadStatus.progress;\n        }\n\n        @Override\n        public int getSmallSofarBytes() {\n            return sofarBytes;\n        }\n\n        @Override\n        public int describeContents() {\n            return 0;\n        }\n\n        @Override\n        public void writeToParcel(Parcel dest, int flags) {\n            super.writeToParcel(dest, flags);\n            dest.writeInt(this.sofarBytes);\n        }\n\n        ProgressMessageSnapshot(Parcel in) {\n            super(in);\n            this.sofarBytes = in.readInt();\n        }\n    }\n\n    // Completed Snapshot\n    public static class CompletedFlowDirectlySnapshot extends CompletedSnapshot implements\n            IFlowDirectly {\n\n        CompletedFlowDirectlySnapshot(int id, boolean reusedDownloadedFile,\n                                      int totalBytes) {\n            super(id, reusedDownloadedFile, totalBytes);\n        }\n\n        CompletedFlowDirectlySnapshot(Parcel in) {\n            super(in);\n        }\n    }\n\n    public static class CompletedSnapshot extends SmallMessageSnapshot {\n        private final boolean reusedDownloadedFile;\n        private final int totalBytes;\n\n        CompletedSnapshot(int id, boolean reusedDownloadedFile,\n                          int totalBytes) {\n            super(id);\n            this.reusedDownloadedFile = reusedDownloadedFile;\n            this.totalBytes = totalBytes;\n        }\n\n        @Override\n        public int describeContents() {\n            return 0;\n        }\n\n        @Override\n        public void writeToParcel(Parcel dest, int flags) {\n            super.writeToParcel(dest, flags);\n            dest.writeByte(reusedDownloadedFile ? (byte) 1 : (byte) 0);\n            dest.writeInt(this.totalBytes);\n        }\n\n        CompletedSnapshot(Parcel in) {\n            super(in);\n            this.reusedDownloadedFile = in.readByte() != 0;\n            this.totalBytes = in.readInt();\n        }\n\n        @Override\n        public byte getStatus() {\n            return FileDownloadStatus.completed;\n        }\n\n        @Override\n        public int getSmallTotalBytes() {\n            return totalBytes;\n        }\n\n        @Override\n        public boolean isReusedDownloadedFile() {\n            return reusedDownloadedFile;\n        }\n    }\n\n    // Error Snapshot\n    public static class ErrorMessageSnapshot extends SmallMessageSnapshot {\n        private final int sofarBytes;\n        private final Throwable throwable;\n\n        ErrorMessageSnapshot(int id, int sofarBytes, Throwable throwable) {\n            super(id);\n            this.sofarBytes = sofarBytes;\n            this.throwable = throwable;\n        }\n\n        @Override\n        public int getSmallSofarBytes() {\n            return sofarBytes;\n        }\n\n        @Override\n        public byte getStatus() {\n            return FileDownloadStatus.error;\n        }\n\n        @Override\n        public Throwable getThrowable() {\n            return throwable;\n        }\n\n        @Override\n        public int describeContents() {\n            return 0;\n        }\n\n        @Override\n        public void writeToParcel(Parcel dest, int flags) {\n            super.writeToParcel(dest, flags);\n            dest.writeInt(this.sofarBytes);\n            dest.writeSerializable(this.throwable);\n        }\n\n        ErrorMessageSnapshot(Parcel in) {\n            super(in);\n            this.sofarBytes = in.readInt();\n            this.throwable = (Throwable) in.readSerializable();\n        }\n    }\n\n    // Retry Snapshot\n    public static class RetryMessageSnapshot extends ErrorMessageSnapshot {\n        private final int retryingTimes;\n\n        RetryMessageSnapshot(int id, int sofarBytes, Throwable throwable,\n                             int retryingTimes) {\n            super(id, sofarBytes, throwable);\n            this.retryingTimes = retryingTimes;\n        }\n\n        @Override\n        public int getRetryingTimes() {\n            return retryingTimes;\n        }\n\n        @Override\n        public int describeContents() {\n            return 0;\n        }\n\n        @Override\n        public void writeToParcel(Parcel dest, int flags) {\n            super.writeToParcel(dest, flags);\n            dest.writeInt(this.retryingTimes);\n        }\n\n        RetryMessageSnapshot(Parcel in) {\n            super(in);\n            this.retryingTimes = in.readInt();\n        }\n\n        @Override\n        public byte getStatus() {\n            return FileDownloadStatus.retry;\n        }\n    }\n\n\n    // Warn Snapshot\n    public static class WarnFlowDirectlySnapshot extends WarnMessageSnapshot implements\n            IFlowDirectly {\n        WarnFlowDirectlySnapshot(int id, int sofarBytes, int totalBytes) {\n            super(id, sofarBytes, totalBytes);\n        }\n\n        WarnFlowDirectlySnapshot(Parcel in) {\n            super(in);\n        }\n    }\n\n    public static class WarnMessageSnapshot extends PendingMessageSnapshot implements\n            IWarnMessageSnapshot {\n\n        WarnMessageSnapshot(int id, int sofarBytes, int totalBytes) {\n            super(id, sofarBytes, totalBytes);\n        }\n\n        WarnMessageSnapshot(Parcel in) {\n            super(in);\n        }\n\n        @Override\n        public MessageSnapshot turnToPending() {\n            return new SmallMessageSnapshot.PendingMessageSnapshot(this);\n        }\n\n        @Override\n        public byte getStatus() {\n            return FileDownloadStatus.warn;\n        }\n    }\n\n    // Paused Snapshot\n    public static class PausedSnapshot extends PendingMessageSnapshot {\n        PausedSnapshot(int id, int sofarBytes, int totalBytes) {\n            super(id, sofarBytes, totalBytes);\n        }\n\n        @Override\n        public byte getStatus() {\n            return FileDownloadStatus.paused;\n        }\n    }\n}\n"
  },
  {
    "path": "library/src/main/java/com/liulishuo/filedownloader/model/ConnectionModel.java",
    "content": "/*\n * Copyright (c) 2015 LingoChamp Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *   http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.liulishuo.filedownloader.model;\n\nimport android.content.ContentValues;\n\nimport com.liulishuo.filedownloader.util.FileDownloadUtils;\n\nimport java.util.List;\n\n/**\n * The connection model used for record each connections on multiple connections case.\n */\n\npublic class ConnectionModel {\n    public static final String ID = \"id\";\n    private int id;\n\n    public static final String INDEX = \"connectionIndex\";\n    private int index;\n\n    public static final String START_OFFSET = \"startOffset\";\n    private long startOffset;\n\n    public static final String CURRENT_OFFSET = \"currentOffset\";\n    private long currentOffset;\n\n    public static final String END_OFFSET = \"endOffset\";\n    private long endOffset;\n\n    public int getId() {\n        return id;\n    }\n\n    public void setId(int id) {\n        this.id = id;\n    }\n\n    public int getIndex() {\n        return index;\n    }\n\n    public void setIndex(int index) {\n        this.index = index;\n    }\n\n    public long getStartOffset() {\n        return startOffset;\n    }\n\n    public void setStartOffset(long startOffset) {\n        this.startOffset = startOffset;\n    }\n\n    public long getCurrentOffset() {\n        return currentOffset;\n    }\n\n    public void setCurrentOffset(long currentOffset) {\n        this.currentOffset = currentOffset;\n    }\n\n    public long getEndOffset() {\n        return endOffset;\n    }\n\n    public void setEndOffset(long endOffset) {\n        this.endOffset = endOffset;\n    }\n\n    public ContentValues toContentValues() {\n        final ContentValues values = new ContentValues();\n        values.put(ConnectionModel.ID, id);\n        values.put(ConnectionModel.INDEX, index);\n        values.put(ConnectionModel.START_OFFSET, startOffset);\n        values.put(ConnectionModel.CURRENT_OFFSET, currentOffset);\n        values.put(ConnectionModel.END_OFFSET, endOffset);\n        return values;\n    }\n\n    public static long getTotalOffset(List<ConnectionModel> modelList) {\n        long totalOffset = 0;\n        for (ConnectionModel model : modelList) {\n            totalOffset += (model.getCurrentOffset() - model.getStartOffset());\n        }\n        return totalOffset;\n    }\n\n    @Override\n    public String toString() {\n        return FileDownloadUtils.formatString(\"id[%d] index[%d] range[%d, %d) current offset(%d)\",\n                id, index, startOffset, endOffset, currentOffset);\n    }\n}\n"
  },
  {
    "path": "library/src/main/java/com/liulishuo/filedownloader/model/FileDownloadHeader.java",
    "content": "/*\n * Copyright (c) 2015 LingoChamp Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *    http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.liulishuo.filedownloader.model;\n\nimport android.os.Parcel;\nimport android.os.Parcelable;\n\nimport java.util.ArrayList;\nimport java.util.HashMap;\nimport java.util.List;\n\n/**\n * We have already handled Etag internal for guaranteeing tasks resuming from the breakpoint, in\n * other words, if the task has downloaded and got Etag, we will add the 'If-Match' and the 'Range'\n * K-V to its request header automatically.\n */\npublic class FileDownloadHeader implements Parcelable {\n\n    private HashMap<String, List<String>> mHeaderMap;\n\n    /**\n     * We have already handled etag, and will add 'If-Match' & 'Range' value if it works.\n     *\n     * @see com.liulishuo.filedownloader.download.ConnectTask#addUserRequiredHeader\n     */\n    public void add(String name, String value) {\n        if (name == null) throw new NullPointerException(\"name == null\");\n        if (name.isEmpty()) throw new IllegalArgumentException(\"name is empty\");\n        if (value == null) throw new NullPointerException(\"value == null\");\n\n        if (mHeaderMap == null) {\n            mHeaderMap = new HashMap<>();\n        }\n\n        List<String> values = mHeaderMap.get(name);\n        if (values == null) {\n            values = new ArrayList<>();\n            mHeaderMap.put(name, values);\n        }\n\n        if (!values.contains(value)) {\n            values.add(value);\n        }\n    }\n\n    /**\n     * We have already handled etag, and will add 'If-Match' & 'Range' value if it works.\n     *\n     * @see com.liulishuo.filedownloader.download.ConnectTask#addUserRequiredHeader\n     */\n    public void add(String line) {\n        String[] parsed = line.split(\":\");\n        final String name = parsed[0].trim();\n        final String value = parsed[1].trim();\n\n        add(name, value);\n    }\n\n    /**\n     * Remove all files with the name.\n     */\n    public void removeAll(String name) {\n        if (mHeaderMap == null) {\n            return;\n        }\n\n        mHeaderMap.remove(name);\n    }\n\n    @Override\n    public int describeContents() {\n        return 0;\n    }\n\n    @Override\n    public void writeToParcel(Parcel dest, int flags) {\n        dest.writeMap(mHeaderMap);\n    }\n\n    public HashMap<String, List<String>> getHeaders() {\n        return mHeaderMap;\n    }\n\n    public FileDownloadHeader() {\n    }\n\n    protected FileDownloadHeader(Parcel in) {\n        //noinspection unchecked\n        this.mHeaderMap = in.readHashMap(String.class.getClassLoader());\n    }\n\n    public static final Creator<FileDownloadHeader> CREATOR = new Creator<FileDownloadHeader>() {\n        public FileDownloadHeader createFromParcel(Parcel source) {\n            return new FileDownloadHeader(source);\n        }\n\n        public FileDownloadHeader[] newArray(int size) {\n            return new FileDownloadHeader[size];\n        }\n    };\n\n    @Override\n    public String toString() {\n        return mHeaderMap.toString();\n    }\n}\n"
  },
  {
    "path": "library/src/main/java/com/liulishuo/filedownloader/model/FileDownloadModel.java",
    "content": "/*\n * Copyright (c) 2015 LingoChamp Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *    http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.liulishuo.filedownloader.model;\n\nimport android.content.ContentValues;\nimport android.os.Parcel;\nimport android.os.Parcelable;\n\nimport com.liulishuo.filedownloader.BaseDownloadTask;\nimport com.liulishuo.filedownloader.database.FileDownloadDatabase;\nimport com.liulishuo.filedownloader.util.FileDownloadUtils;\n\nimport java.io.File;\nimport java.util.concurrent.atomic.AtomicInteger;\nimport java.util.concurrent.atomic.AtomicLong;\n\n/**\n * The model of the downloading task will be used in the filedownloader database.\n *\n * @see FileDownloadDatabase\n */\n@SuppressWarnings(\"WeakerAccess\")\npublic class FileDownloadModel implements Parcelable {\n\n    public static final int TOTAL_VALUE_IN_CHUNKED_RESOURCE = -1;\n    public static final int DEFAULT_CALLBACK_PROGRESS_TIMES = 100;\n\n    // download id\n    private int id;\n    public static final String ID = \"_id\";\n\n    // download url\n    private String url;\n    public static final String URL = \"url\";\n\n    // save path\n    private String path;\n    public static final String PATH = \"path\";\n\n\n    private boolean pathAsDirectory;\n    public static final String PATH_AS_DIRECTORY = \"pathAsDirectory\";\n\n    private String filename;\n    public static final String FILENAME = \"filename\";\n\n    private final AtomicInteger status;\n    public static final String STATUS = \"status\";\n\n    private final AtomicLong soFar;\n    private long total;\n\n    public static final String SOFAR = \"sofar\";\n    public static final String TOTAL = \"total\";\n\n    private String errMsg;\n    public static final String ERR_MSG = \"errMsg\";\n\n    // header\n    private String eTag;\n    public static final String ETAG = \"etag\";\n\n    private int connectionCount;\n    public static final String CONNECTION_COUNT = \"connectionCount\";\n\n    public void setId(int id) {\n        this.id = id;\n    }\n\n    public void setUrl(String url) {\n        this.url = url;\n    }\n\n    public void setPath(String path, boolean pathAsDirectory) {\n        this.path = path;\n        this.pathAsDirectory = pathAsDirectory;\n    }\n\n    public void setStatus(byte status) {\n        this.status.set(status);\n    }\n\n    public void setSoFar(long soFar) {\n        this.soFar.set(soFar);\n    }\n\n    public void increaseSoFar(long increaseBytes) {\n        this.soFar.addAndGet(increaseBytes);\n    }\n\n    public void setTotal(long total) {\n        this.isLargeFile = total > Integer.MAX_VALUE;\n        this.total = total;\n    }\n\n    public int getId() {\n        return id;\n    }\n\n    public String getUrl() {\n        return url;\n    }\n\n    /**\n     * Get the path user set from {@link BaseDownloadTask#setPath(String)}\n     *\n     * @return the path user set from {@link BaseDownloadTask#setPath(String)}\n     * @see #getTargetFilePath()\n     */\n    public String getPath() {\n        return path;\n    }\n\n    /**\n     * Get the finally target file path is used for store the download file.\n     * <p/>\n     * This path is composited with {@link #path}、{@link #pathAsDirectory}、{@link #filename}.\n     * <p/>\n     * Why {@link #getPath()} may be not equal to getTargetFilePath()? this case only occurred\n     * when the {@link #isPathAsDirectory()} is {@code true}, on this scenario the\n     * {@link #getPath()} is directory, and the getTargetFilePath() is 'directory + \"/\" + filename'.\n     *\n     * @return the finally target file path.\n     */\n    public String getTargetFilePath() {\n        return FileDownloadUtils.getTargetFilePath(getPath(), isPathAsDirectory(), getFilename());\n    }\n\n    public String getTempFilePath() {\n        if (getTargetFilePath() == null) {\n            return null;\n        }\n        return FileDownloadUtils.getTempPath(getTargetFilePath());\n    }\n\n    public byte getStatus() {\n        return (byte) status.get();\n    }\n\n    public long getSoFar() {\n        return soFar.get();\n    }\n\n    public long getTotal() {\n        return total;\n    }\n\n    public boolean isChunked() {\n        return total == TOTAL_VALUE_IN_CHUNKED_RESOURCE;\n    }\n\n    public String getETag() {\n        return eTag;\n    }\n\n    public void setETag(String eTag) {\n        this.eTag = eTag;\n    }\n\n    public String getErrMsg() {\n        return errMsg;\n    }\n\n    public void setErrMsg(String errMsg) {\n        this.errMsg = errMsg;\n    }\n\n    public void setFilename(String filename) {\n        this.filename = filename;\n    }\n\n    public boolean isPathAsDirectory() {\n        return pathAsDirectory;\n    }\n\n    public String getFilename() {\n        return filename;\n    }\n\n    public void setConnectionCount(int connectionCount) {\n        this.connectionCount = connectionCount;\n    }\n\n    public int getConnectionCount() {\n        return connectionCount;\n    }\n\n    /**\n     * reset the connection count to default value: 1.\n     */\n    public void resetConnectionCount() {\n        this.connectionCount = 1;\n    }\n\n    public ContentValues toContentValues() {\n        ContentValues cv = new ContentValues();\n        cv.put(ID, getId());\n        cv.put(URL, getUrl());\n        cv.put(PATH, getPath());\n        cv.put(STATUS, getStatus());\n        cv.put(SOFAR, getSoFar());\n        cv.put(TOTAL, getTotal());\n        cv.put(ERR_MSG, getErrMsg());\n        cv.put(ETAG, getETag());\n        cv.put(CONNECTION_COUNT, getConnectionCount());\n        cv.put(PATH_AS_DIRECTORY, isPathAsDirectory());\n        if (isPathAsDirectory() && getFilename() != null) {\n            cv.put(FILENAME, getFilename());\n        }\n\n        return cv;\n    }\n\n\n    private boolean isLargeFile;\n\n    public boolean isLargeFile() {\n        return isLargeFile;\n    }\n\n    public void deleteTaskFiles() {\n        deleteTempFile();\n        deleteTargetFile();\n    }\n\n    public void deleteTempFile() {\n        final String tempFilePath = getTempFilePath();\n\n        if (tempFilePath != null) {\n            final File tempFile = new File(tempFilePath);\n            if (tempFile.exists()) {\n                //noinspection ResultOfMethodCallIgnored\n                tempFile.delete();\n            }\n        }\n    }\n\n    public void deleteTargetFile() {\n        final String targetFilePath = getTargetFilePath();\n        if (targetFilePath != null) {\n            final File targetFile = new File(targetFilePath);\n            if (targetFile.exists()) {\n                //noinspection ResultOfMethodCallIgnored\n                targetFile.delete();\n            }\n        }\n    }\n\n    @Override\n    public String toString() {\n        return FileDownloadUtils.formatString(\"id[%d], url[%s], path[%s], status[%d], sofar[%s],\"\n                        + \" total[%d], etag[%s], %s\",\n                id, url, path, status.get(), soFar, total, eTag,\n                super.toString());\n    }\n\n\n    @Override\n    public int describeContents() {\n        return 0;\n    }\n\n    @Override\n    public void writeToParcel(Parcel dest, int flags) {\n        dest.writeInt(this.id);\n        dest.writeString(this.url);\n        dest.writeString(this.path);\n        dest.writeByte(this.pathAsDirectory ? (byte) 1 : (byte) 0);\n        dest.writeString(this.filename);\n        dest.writeByte((byte) this.status.get());\n        dest.writeLong(this.soFar.get());\n        dest.writeLong(this.total);\n        dest.writeString(this.errMsg);\n        dest.writeString(this.eTag);\n        dest.writeInt(this.connectionCount);\n        dest.writeByte(this.isLargeFile ? (byte) 1 : (byte) 0);\n    }\n\n    public FileDownloadModel() {\n        this.soFar = new AtomicLong();\n        this.status = new AtomicInteger();\n    }\n\n    protected FileDownloadModel(Parcel in) {\n        this.id = in.readInt();\n        this.url = in.readString();\n        this.path = in.readString();\n        this.pathAsDirectory = in.readByte() != 0;\n        this.filename = in.readString();\n        this.status = new AtomicInteger(in.readByte());\n        this.soFar = new AtomicLong(in.readLong());\n        this.total = in.readLong();\n        this.errMsg = in.readString();\n        this.eTag = in.readString();\n        this.connectionCount = in.readInt();\n        this.isLargeFile = in.readByte() != 0;\n    }\n\n    public static final Parcelable.Creator<FileDownloadModel> CREATOR =\n            new Parcelable.Creator<FileDownloadModel>() {\n                @Override\n                public FileDownloadModel createFromParcel(Parcel source) {\n                    return new FileDownloadModel(source);\n                }\n\n                @Override\n                public FileDownloadModel[] newArray(int size) {\n                    return new FileDownloadModel[size];\n                }\n            };\n}\n"
  },
  {
    "path": "library/src/main/java/com/liulishuo/filedownloader/model/FileDownloadStatus.java",
    "content": "/*\n * Copyright (c) 2015 LingoChamp Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *    http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.liulishuo.filedownloader.model;\n\nimport com.liulishuo.filedownloader.BaseDownloadTask;\n\n@SuppressWarnings({\"checkstyle:linelength\", \"checkstyle:constantname\"})\n/**\n * The downloading status.\n *\n * @see com.liulishuo.filedownloader.IFileDownloadMessenger\n * @see <a href=\"https://raw.githubusercontent.com/lingochamp/FileDownloader/master/art/filedownloadlistener_callback_flow.png\">Callback-Flow</a>\n */\npublic class FileDownloadStatus {\n    // [-2^7, 2^7 -1]\n    // by very beginning\n    /**\n     * When the task on {@code toLaunchPool} status, it means that the task is just into the\n     * LaunchPool and is scheduled for launch.\n     * <p>\n     * The task is scheduled for launch and it isn't on the FileDownloadService yet.\n     */\n    public static final byte toLaunchPool = 10;\n    /**\n     * When the task on {@code toFileDownloadService} status, it means that the task is just post to\n     * the FileDownloadService.\n     * <p>\n     * The task is posting to the FileDownloadService and after this status, this task can start.\n     */\n    public static final byte toFileDownloadService = 11;\n\n    // by FileDownloadService\n    /**\n     * When the task on {@code pending} status, it means that the task is in the list on the\n     * FileDownloadService and just waiting for start.\n     * <p>\n     * The task is waiting on the FileDownloadService.\n     * <p>\n     * The count of downloading simultaneously, you can configure in filedownloader.properties.\n     */\n    public static final byte pending = 1;\n    /**\n     * When the task on {@code started} status, it means that the network access thread of\n     * downloading this task is started.\n     * <p>\n     * The task is downloading on the FileDownloadService.\n     */\n    public static final byte started = 6;\n    /**\n     * When the task on {@code connected} status, it means that the task is successfully connected\n     * to the back-end.\n     * <p>\n     * The task is downloading on the FileDownloadService.\n     */\n    public static final byte connected = 2;\n    /**\n     * When the task on {@code progress} status, it means that the task is fetching data from the\n     * back-end.\n     * <p>\n     * The task is downloading on the FileDownloadService.\n     */\n    public static final byte progress = 3;\n    /**\n     * When the task on {@code blockComplete} status, it means that the task has been completed\n     * downloading successfully.\n     * <p>\n     * The task is completed downloading successfully and the action-flow is blocked for doing\n     * something before callback completed method.\n     */\n    public static final byte blockComplete = 4;\n    /**\n     * When the task on {@code retry} status, it means that the task must occur some error, but\n     * there is a valid chance to retry, so the task is retry to download again.\n     * <p>\n     * The task is restarting on the FileDownloadService.\n     */\n    public static final byte retry = 5;\n\n    /**\n     * When the task on {@code error} status, it means that the task must occur some error and there\n     * isn't any valid chance to retry, so the task is finished with error.\n     * <p>\n     * The task is finished with an error.\n     */\n    public static final byte error = -1;\n    /**\n     * When the task on {@code paused} status, it means that the task is paused manually.\n     * <p>\n     * The task is finished with the pause action.\n     */\n    public static final byte paused = -2;\n    /**\n     * When the task on {@code completed} status, it means that the task is completed downloading\n     * successfully.\n     * <p>\n     * The task is finished with completed downloading successfully.\n     */\n    public static final byte completed = -3;\n    /**\n     * When the task on {@code warn} status, it means that there is another same task(same url,\n     * same path to store content) is running.\n     * <p>\n     * The task is finished with the warn status.\n     */\n    public static final byte warn = -4;\n\n    /**\n     * When the task on {@code INVALID_STATUS} status, it means that the task is IDLE.\n     * <p>\n     * The task is clear and it isn't launched.\n     */\n    public static final byte INVALID_STATUS = 0;\n\n    public static boolean isOver(final int status) {\n        return status < 0;\n    }\n\n    public static boolean isIng(final int status) {\n        return status > 0;\n    }\n\n    public static boolean isKeepAhead(final int status, final int nextStatus) {\n        if (status != progress && status != retry && status == nextStatus) {\n            return false;\n        }\n\n        if (isOver(status)) {\n            return false;\n        }\n\n        if (status >= pending && status <= started /** in FileDownloadService **/\n                && nextStatus >= toLaunchPool && nextStatus <= toFileDownloadService) {\n            return false;\n        }\n\n        switch (status) {\n            case pending:\n                switch (nextStatus) {\n                    case INVALID_STATUS:\n                        return false;\n                    default:\n                        return true;\n                }\n            case started:\n                switch (nextStatus) {\n                    case INVALID_STATUS:\n                    case pending:\n                        return false;\n                    default:\n                        return true;\n                }\n\n            case connected:\n                switch (nextStatus) {\n                    case INVALID_STATUS:\n                    case pending:\n                    case started:\n                        return false;\n                    default:\n                        return true;\n                }\n            case progress:\n                switch (nextStatus) {\n                    case INVALID_STATUS:\n                    case pending:\n                    case started:\n                    case connected:\n                        return false;\n                    default:\n                        return true;\n                }\n\n            case retry:\n                switch (nextStatus) {\n                    case pending:\n                    case started:\n                        return false;\n                    default:\n                        return true;\n                }\n\n            default:\n                return true;\n        }\n\n    }\n\n    @SuppressWarnings(\"checkstyle:avoidnestedblocks\")\n    public static boolean isKeepFlow(final int status, final int nextStatus) {\n        if (status != progress && status != retry && status == nextStatus) {\n            return false;\n        }\n\n        if (isOver(status)) {\n            return false;\n        }\n\n        if (nextStatus == paused) {\n            return true;\n        }\n\n        if (nextStatus == error) {\n            return true;\n        }\n\n        switch (status) {\n            case INVALID_STATUS: {\n                switch (nextStatus) {\n                    case toLaunchPool:\n                        return true;\n                    default:\n                        return false;\n                }\n            }\n            case toLaunchPool:\n                switch (nextStatus) {\n                    case toFileDownloadService:\n                        return true;\n                    default:\n                        return false;\n                }\n            case toFileDownloadService:\n                switch (nextStatus) {\n                    case pending:\n                    case warn:\n                    case completed:\n                        return true;\n                    default:\n                        return false;\n                }\n            case pending:\n                switch (nextStatus) {\n                    case started:\n                        return true;\n                    default:\n                        return false;\n                }\n            case retry:\n            case started:\n                switch (nextStatus) {\n                    case retry:\n                    case connected:\n                        return true;\n                    default:\n                        return false;\n                }\n            case connected:\n            case progress:\n                switch (nextStatus) {\n                    case progress:\n                    case completed:\n                    case retry:\n                        return true;\n                    default:\n                        return false;\n                }\n            default:\n                return false;\n        }\n\n    }\n\n    public static boolean isMoreLikelyCompleted(BaseDownloadTask task) {\n        return task.getStatus() == INVALID_STATUS || task.getStatus() == progress;\n    }\n}\n"
  },
  {
    "path": "library/src/main/java/com/liulishuo/filedownloader/model/FileDownloadTaskAtom.java",
    "content": "/*\n * Copyright (c) 2015 LingoChamp Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *    http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage com.liulishuo.filedownloader.model;\n\nimport android.os.Parcel;\nimport android.os.Parcelable;\n\nimport com.liulishuo.filedownloader.FileDownloader;\nimport com.liulishuo.filedownloader.util.FileDownloadUtils;\n\n/**\n * The Minimal unit for a task.\n * <p/>\n * Used for telling the FileDownloader Engine that a task was downloaded by the other ways.\n *\n * @see com.liulishuo.filedownloader.FileDownloader#setTaskCompleted\n * @deprecated No used. {@link FileDownloader#setTaskCompleted(String, String, long)}\n */\n@SuppressWarnings({\"WeakerAccess\", \"deprecation\", \"DeprecatedIsStillUsed\"})\npublic class FileDownloadTaskAtom implements Parcelable {\n    private String url;\n    private String path;\n    private long totalBytes;\n\n    public FileDownloadTaskAtom(String url, String path, long totalBytes) {\n        setUrl(url);\n        setPath(path);\n        setTotalBytes(totalBytes);\n    }\n\n    private int id;\n\n    public int getId() {\n        if (id != 0) {\n            return id;\n        }\n\n        return id = FileDownloadUtils.generateId(getUrl(), getPath());\n    }\n\n    public String getUrl() {\n        return url;\n    }\n\n    public void setUrl(String url) {\n        this.url = url;\n    }\n\n    public String getPath() {\n        return path;\n    }\n\n    public void setPath(String path) {\n        this.path = path;\n    }\n\n    public long getTotalBytes() {\n        return totalBytes;\n    }\n\n    public void setTotalBytes(long totalBytes) {\n        this.totalBytes = totalBytes;\n    }\n\n    @Override\n    public int describeContents() {\n        return 0;\n    }\n\n    @Override\n    public void writeToParcel(Parcel dest, int flags) {\n        dest.writeString(this.url);\n        dest.writeString(this.path);\n        dest.writeLong(this.totalBytes);\n    }\n\n    protected FileDownloadTaskAtom(Parcel in) {\n        this.url = in.readString();\n        this.path = in.readString();\n        this.totalBytes = in.readLong();\n    }\n\n    public static final Parcelable.Creator<FileDownloadTaskAtom> CREATOR =\n            new Parcelable.Creator<FileDownloadTaskAtom>() {\n                @Override\n                public FileDownloadTaskAtom createFromParcel(Parcel source) {\n                    return new FileDownloadTaskAtom(source);\n                }\n\n                @Override\n                public FileDownloadTaskAtom[] newArray(int size) {\n                    return new FileDownloadTaskAtom[size];\n                }\n            };\n}\n"
  },
  {
    "path": "library/src/main/java/com/liulishuo/filedownloader/notification/BaseNotificationItem.java",
    "content": "/*\n * Copyright (c) 2015 LingoChamp Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *    http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.liulishuo.filedownloader.notification;\n\nimport android.app.NotificationManager;\nimport android.content.Context;\n\nimport com.liulishuo.filedownloader.model.FileDownloadStatus;\nimport com.liulishuo.filedownloader.util.FileDownloadHelper;\n\n/**\n * An atom notification item which identify with a downloading task, they have the same downloading\n * Id.\n *\n * @see FileDownloadNotificationHelper\n * @see FileDownloadNotificationListener\n */\n@SuppressWarnings(\"WeakerAccess\")\npublic abstract class BaseNotificationItem {\n\n    private int id, sofar, total;\n    private String title, desc;\n\n    private int status = FileDownloadStatus.INVALID_STATUS;\n    private int lastStatus = FileDownloadStatus.INVALID_STATUS;\n\n    public BaseNotificationItem(final int id, final String title, final String desc) {\n        this.id = id;\n\n        this.title = title;\n        this.desc = desc;\n    }\n\n    public void show(boolean isShowProgress) {\n        show(isChanged(), getStatus(), isShowProgress);\n    }\n\n    /**\n     * @param isShowProgress Whether there is a need to show the progress schedule changes\n     */\n    public abstract void show(boolean statusChanged, int status, boolean isShowProgress);\n\n    public void update(final int sofar, final int total) {\n        this.sofar = sofar;\n        this.total = total;\n        show(true);\n    }\n\n    public void updateStatus(final int status) {\n        this.status = status;\n    }\n\n    public void cancel() {\n        getManager().cancel(id);\n    }\n\n    private NotificationManager manager;\n\n    protected NotificationManager getManager() {\n        if (manager == null) {\n            manager = (NotificationManager) FileDownloadHelper.getAppContext().\n                    getSystemService(Context.NOTIFICATION_SERVICE);\n        }\n        return manager;\n    }\n\n    public int getId() {\n        return this.id;\n    }\n\n    public void setId(int id) {\n        this.id = id;\n    }\n\n    public int getSofar() {\n        return sofar;\n    }\n\n    public void setSofar(int sofar) {\n        this.sofar = sofar;\n    }\n\n    public int getTotal() {\n        return total;\n    }\n\n    public void setTotal(int total) {\n        this.total = total;\n    }\n\n    public String getTitle() {\n        return title;\n    }\n\n    public void setTitle(String title) {\n        this.title = title;\n    }\n\n    public String getDesc() {\n        return desc;\n    }\n\n    public void setDesc(String desc) {\n        this.desc = desc;\n    }\n\n    public int getStatus() {\n        this.lastStatus = status;\n        return status;\n    }\n\n    public void setStatus(int status) {\n        this.status = status;\n    }\n\n    public int getLastStatus() {\n        return lastStatus;\n    }\n\n    public boolean isChanged() {\n        return this.lastStatus != status;\n    }\n}"
  },
  {
    "path": "library/src/main/java/com/liulishuo/filedownloader/notification/FileDownloadNotificationHelper.java",
    "content": "/*\n * Copyright (c) 2015 LingoChamp Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *    http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.liulishuo.filedownloader.notification;\n\nimport android.util.SparseArray;\n\nimport com.liulishuo.filedownloader.model.FileDownloadStatus;\n\n/**\n * The helper for notifications with downloading tasks. You also can think this is the notifications\n * manager.\n *\n * @see BaseNotificationItem\n * @see FileDownloadNotificationListener\n */\n@SuppressWarnings(\"WeakerAccess\")\npublic class FileDownloadNotificationHelper<T extends BaseNotificationItem> {\n\n    private final SparseArray<T> notificationArray = new SparseArray<>();\n\n    /**\n     * Get {@link BaseNotificationItem} by the download id.\n     *\n     * @param id The download id.\n     */\n    public T get(final int id) {\n        return notificationArray.get(id);\n    }\n\n    public boolean contains(final int id) {\n        return get(id) != null;\n    }\n\n    /**\n     * Remove the {@link BaseNotificationItem} by the download id.\n     *\n     * @param id The download id.\n     * @return The removed {@link BaseNotificationItem}.\n     */\n    public T remove(final int id) {\n        final T n = get(id);\n        if (n != null) {\n            notificationArray.remove(id);\n            return n;\n        }\n\n        return null;\n    }\n\n    /**\n     * Input a {@link BaseNotificationItem}.\n     */\n    public void add(T notification) {\n        notificationArray.remove(notification.getId());\n        notificationArray.put(notification.getId(), notification);\n    }\n\n    /**\n     * Show the notification with the exact progress.\n     *\n     * @param id    The download id.\n     * @param sofar The downloaded bytes so far.\n     * @param total The total bytes of this task.\n     */\n    public void showProgress(final int id, final int sofar, final int total) {\n        final T notification = get(id);\n\n        if (notification == null) {\n            return;\n        }\n\n        notification.updateStatus(FileDownloadStatus.progress);\n        notification.update(sofar, total);\n    }\n\n    /**\n     * Show the notification with indeterminate progress.\n     *\n     * @param id     The download id.\n     * @param status {@link FileDownloadStatus}\n     */\n    public void showIndeterminate(final int id, int status) {\n        final BaseNotificationItem notification = get(id);\n\n        if (notification == null) {\n            return;\n        }\n\n        notification.updateStatus(status);\n        notification.show(false);\n    }\n\n    /**\n     * Cancel the notification by notification id.\n     *\n     * @param id The download id.\n     */\n    public void cancel(final int id) {\n        final BaseNotificationItem notification = remove(id);\n\n        if (notification == null) {\n            return;\n        }\n\n        notification.cancel();\n    }\n\n    /**\n     * Clear and cancel all notifications which inside this helper {@link #notificationArray}.\n     */\n    public void clear() {\n        @SuppressWarnings(\"unchecked\") SparseArray<BaseNotificationItem> cloneArray =\n                (SparseArray<BaseNotificationItem>) notificationArray.clone();\n        notificationArray.clear();\n\n        for (int i = 0; i < cloneArray.size(); i++) {\n            final BaseNotificationItem n = cloneArray.get(cloneArray.keyAt(i));\n            n.cancel();\n        }\n\n    }\n}\n"
  },
  {
    "path": "library/src/main/java/com/liulishuo/filedownloader/notification/FileDownloadNotificationListener.java",
    "content": "/*\n * Copyright (c) 2015 LingoChamp Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *    http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.liulishuo.filedownloader.notification;\n\nimport com.liulishuo.filedownloader.BaseDownloadTask;\nimport com.liulishuo.filedownloader.FileDownloadList;\nimport com.liulishuo.filedownloader.FileDownloadListener;\n\n/**\n * The listener of the notification with the task.\n *\n * @see FileDownloadNotificationHelper\n * @see BaseNotificationItem\n */\n@SuppressWarnings({\"WeakerAccess\", \"UnusedParameters\"})\npublic abstract class FileDownloadNotificationListener extends FileDownloadListener {\n    private final FileDownloadNotificationHelper helper;\n\n    public FileDownloadNotificationListener(FileDownloadNotificationHelper helper) {\n        if (helper == null) throw new IllegalArgumentException(\"helper must not be null!\");\n        this.helper = helper;\n    }\n\n    public FileDownloadNotificationHelper getHelper() {\n        return helper;\n    }\n\n\n    public void addNotificationItem(int downloadId) {\n        if (downloadId == 0) {\n            return;\n        }\n\n        BaseDownloadTask.IRunningTask task = FileDownloadList.getImpl().get(downloadId);\n        if (task != null) {\n            addNotificationItem(task.getOrigin());\n        }\n    }\n\n    public void addNotificationItem(BaseDownloadTask task) {\n        if (disableNotification(task)) {\n            return;\n        }\n\n        final BaseNotificationItem n = create(task);\n        if (n != null) {\n            //noinspection unchecked\n            this.helper.add(n);\n        }\n    }\n\n    /**\n     * The notification item with the {@code task} is told to destroy.\n     *\n     * @param task The task used to identify the will be destroyed notification item.\n     */\n    public void destroyNotification(BaseDownloadTask task) {\n        if (disableNotification(task)) {\n            return;\n        }\n\n        this.helper.showIndeterminate(task.getId(), task.getStatus());\n\n        final BaseNotificationItem n = this.helper.\n                remove(task.getId());\n        if (!interceptCancel(task, n) && n != null) {\n            n.cancel();\n        }\n    }\n\n    public void showIndeterminate(BaseDownloadTask task) {\n        if (disableNotification(task)) {\n            return;\n        }\n\n        this.helper.showIndeterminate(task.getId(), task.getStatus());\n    }\n\n    public void showProgress(BaseDownloadTask task, int soFarBytes,\n                             int totalBytes) {\n        if (disableNotification(task)) {\n            return;\n        }\n\n        this.helper.showProgress(task.getId(), task.getSmallFileSoFarBytes(),\n                task.getSmallFileTotalBytes());\n    }\n\n    /**\n     * @param task The task used to bind with the will be created notification item.\n     * @return The notification item is related with the {@code task}.\n     */\n    protected abstract BaseNotificationItem create(BaseDownloadTask task);\n\n    /**\n     * @param task             The task.\n     * @param notificationItem The notification item.\n     * @return {@code true} if you want to survive the notification item, and we will don't  cancel\n     * the relate notification from the notification panel when the relate task is finished,\n     * {@code false} otherwise.\n     * <p>\n     * <strong>Default:</strong> {@code false}\n     * @see #destroyNotification(BaseDownloadTask)\n     */\n    protected boolean interceptCancel(BaseDownloadTask task,\n                                      BaseNotificationItem notificationItem) {\n        return false;\n    }\n\n    /**\n     * @param task The task.\n     * @return {@code true} if you want to disable the internal notification lifecycle, and in this\n     * case all method about the notification will be invalid, {@code false} otherwise.\n     * <p>\n     * <strong>Default:</strong> {@code false}\n     */\n    protected boolean disableNotification(final BaseDownloadTask task) {\n        return false;\n    }\n\n    @Override\n    protected void pending(BaseDownloadTask task, int soFarBytes, int totalBytes) {\n        addNotificationItem(task);\n        showIndeterminate(task);\n    }\n\n    @Override\n    protected void started(BaseDownloadTask task) {\n        super.started(task);\n        showIndeterminate(task);\n    }\n\n    @Override\n    protected void progress(BaseDownloadTask task, int soFarBytes, int totalBytes) {\n        showProgress(task, soFarBytes, totalBytes);\n    }\n\n    @Override\n    protected void retry(BaseDownloadTask task, Throwable ex, int retryingTimes, int soFarBytes) {\n        super.retry(task, ex, retryingTimes, soFarBytes);\n        showIndeterminate(task);\n    }\n\n    @Override\n    protected void blockComplete(BaseDownloadTask task) {\n    }\n\n    @Override\n    protected void completed(BaseDownloadTask task) {\n        destroyNotification(task);\n    }\n\n    @Override\n    protected void paused(BaseDownloadTask task, int soFarBytes, int totalBytes) {\n        destroyNotification(task);\n    }\n\n    @Override\n    protected void error(BaseDownloadTask task, Throwable e) {\n        destroyNotification(task);\n    }\n\n    @Override\n    protected void warn(BaseDownloadTask task) {\n        // ignore\n        // do not handle the case of the same URL and path task(the same download id), which\n        // will share the same notification item\n//        if (!disableNotification(task)) {\n//            destroyNotification(task);\n//        }\n    }\n}\n"
  },
  {
    "path": "library/src/main/java/com/liulishuo/filedownloader/services/BaseFileServiceUIGuard.java",
    "content": "/*\n * Copyright (c) 2015 LingoChamp Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *    http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.liulishuo.filedownloader.services;\n\nimport android.content.ComponentName;\nimport android.content.Context;\nimport android.content.Intent;\nimport android.content.ServiceConnection;\nimport android.os.Binder;\nimport android.os.Build;\nimport android.os.IBinder;\nimport android.os.IInterface;\nimport android.os.RemoteException;\n\nimport com.liulishuo.filedownloader.FileDownloadEventPool;\nimport com.liulishuo.filedownloader.IFileDownloadServiceProxy;\nimport com.liulishuo.filedownloader.event.DownloadServiceConnectChangedEvent;\nimport com.liulishuo.filedownloader.util.ExtraKeys;\nimport com.liulishuo.filedownloader.util.FileDownloadLog;\nimport com.liulishuo.filedownloader.util.FileDownloadUtils;\n\nimport java.util.ArrayList;\nimport java.util.HashMap;\nimport java.util.List;\n\n/**\n * A UI-Guard in Main-Process for IPC, which is the only Object can access the other process in\n * Main-Process with Binder.\n */\npublic abstract class BaseFileServiceUIGuard<CALLBACK extends Binder, INTERFACE extends IInterface>\n        implements IFileDownloadServiceProxy, ServiceConnection {\n\n    private final CALLBACK callback;\n    private volatile INTERFACE service;\n    private final Class<?> serviceClass;\n    protected boolean runServiceForeground = false;\n\n    private final HashMap<String, Object> uiCacheMap = new HashMap<>();\n\n    protected CALLBACK getCallback() {\n        return this.callback;\n    }\n\n    protected INTERFACE getService() {\n        return this.service;\n    }\n\n    protected BaseFileServiceUIGuard(Class<?> serviceClass) {\n        this.serviceClass = serviceClass;\n        this.callback = createCallback();\n    }\n\n    protected abstract CALLBACK createCallback();\n\n    @Override\n    public void onServiceConnected(ComponentName name, IBinder service) {\n        this.service = asInterface(service);\n\n        if (FileDownloadLog.NEED_LOG) {\n            FileDownloadLog.d(this, \"onServiceConnected %s %s\", name, this.service);\n        }\n\n        try {\n            registerCallback(this.service, this.callback);\n        } catch (RemoteException e) {\n            e.printStackTrace();\n        }\n\n        @SuppressWarnings(\"unchecked\") final List<Runnable> runnableList =\n                (List<Runnable>) connectedRunnableList.clone();\n        connectedRunnableList.clear();\n        for (Runnable runnable : runnableList) {\n            runnable.run();\n        }\n\n        FileDownloadEventPool.getImpl().\n                asyncPublishInNewThread(new DownloadServiceConnectChangedEvent(\n                        DownloadServiceConnectChangedEvent.ConnectStatus.connected, serviceClass));\n\n    }\n\n    @Override\n    public void onServiceDisconnected(ComponentName name) {\n        if (FileDownloadLog.NEED_LOG) {\n            FileDownloadLog.d(this, \"onServiceDisconnected %s %s\", name, this.service);\n        }\n        releaseConnect(true);\n    }\n\n    private void releaseConnect(final boolean isLost) {\n        if (!isLost && this.service != null) {\n            try {\n                unregisterCallback(this.service, this.callback);\n            } catch (RemoteException e) {\n                e.printStackTrace();\n            }\n        }\n        if (FileDownloadLog.NEED_LOG) {\n            FileDownloadLog.d(this, \"release connect resources %s\", this.service);\n        }\n        this.service = null;\n\n        FileDownloadEventPool.getImpl().\n                asyncPublishInNewThread(new DownloadServiceConnectChangedEvent(\n                        isLost ? DownloadServiceConnectChangedEvent.ConnectStatus.lost\n                                : DownloadServiceConnectChangedEvent.ConnectStatus.disconnected,\n                        serviceClass));\n    }\n\n    private final List<Context> bindContexts = new ArrayList<>();\n    private final ArrayList<Runnable> connectedRunnableList = new ArrayList<>();\n\n    @Override\n    public void bindStartByContext(final Context context) {\n        bindStartByContext(context, null);\n    }\n\n    @Override\n    public void bindStartByContext(final Context context, final Runnable connectedRunnable) {\n        if (FileDownloadUtils.isDownloaderProcess(context)) {\n            throw new IllegalStateException(\"Fatal-Exception: You can't bind the \"\n                    + \"FileDownloadService in :filedownloader process.\\n It's the invalid operation\"\n                    + \" and is likely to cause unexpected problems.\\n Maybe you want to use\"\n                    + \" non-separate process mode for FileDownloader, More detail about \"\n                    + \"non-separate mode, please move to wiki manually:\"\n                    + \" https://github.com/lingochamp/FileDownloader/wiki/filedownloader.properties\"\n            );\n        }\n\n        if (FileDownloadLog.NEED_LOG) {\n            FileDownloadLog.d(this, \"bindStartByContext %s\", context.getClass().getSimpleName());\n        }\n\n        Intent i = new Intent(context, serviceClass);\n        if (connectedRunnable != null) {\n            if (!connectedRunnableList.contains(connectedRunnable)) {\n                connectedRunnableList.add(connectedRunnable);\n            }\n        }\n\n        if (!bindContexts.contains(context)) {\n            // 对称,只有一次remove，防止内存泄漏\n            bindContexts.add(context);\n        }\n\n        runServiceForeground = FileDownloadUtils.needMakeServiceForeground(context);\n        i.putExtra(ExtraKeys.IS_FOREGROUND, runServiceForeground);\n        context.bindService(i, this, Context.BIND_AUTO_CREATE);\n        if (runServiceForeground) {\n            if (FileDownloadLog.NEED_LOG) FileDownloadLog.d(this, \"start foreground service\");\n            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) context.startForegroundService(i);\n        } else {\n            context.startService(i);\n        }\n    }\n\n    @Override\n    public void unbindByContext(final Context context) {\n        if (!bindContexts.contains(context)) {\n            return;\n        }\n\n        if (FileDownloadLog.NEED_LOG) {\n            FileDownloadLog.d(this, \"unbindByContext %s\", context);\n        }\n\n        bindContexts.remove(context);\n\n\n        if (bindContexts.isEmpty()) {\n            releaseConnect(false);\n        }\n\n        Intent i = new Intent(context, serviceClass);\n        context.unbindService(this);\n        context.stopService(i);\n    }\n\n    @Override\n    public boolean isRunServiceForeground() {\n        return runServiceForeground;\n    }\n\n    protected abstract INTERFACE asInterface(IBinder service);\n\n    protected abstract void registerCallback(final INTERFACE service, final CALLBACK callback)\n            throws RemoteException;\n\n    protected abstract void unregisterCallback(final INTERFACE service, final CALLBACK callback)\n            throws RemoteException;\n\n\n    protected Object popCache(final String key) {\n        return uiCacheMap.remove(key);\n    }\n\n    protected String putCache(final Object object) {\n        if (object == null) {\n            return null;\n        }\n        final String key = object.toString();\n        uiCacheMap.put(key, object);\n        return key;\n    }\n\n    @Override\n    public boolean isConnected() {\n        return getService() != null;\n    }\n\n\n}\n"
  },
  {
    "path": "library/src/main/java/com/liulishuo/filedownloader/services/DefaultIdGenerator.java",
    "content": "/*\n * Copyright (c) 2015 LingoChamp Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *   http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.liulishuo.filedownloader.services;\n\nimport com.liulishuo.filedownloader.util.FileDownloadHelper;\nimport com.liulishuo.filedownloader.util.FileDownloadUtils;\n\nimport static com.liulishuo.filedownloader.util.FileDownloadUtils.formatString;\n\n/**\n * The default id generator.\n */\n\npublic class DefaultIdGenerator implements FileDownloadHelper.IdGenerator {\n\n    @Override\n    public int transOldId(int oldId, String url, String path, boolean pathAsDirectory) {\n        return generateId(url, path, pathAsDirectory);\n    }\n\n    @Override\n    public int generateId(String url, String path, boolean pathAsDirectory) {\n        if (pathAsDirectory) {\n            return FileDownloadUtils.md5(formatString(\"%sp%s@dir\", url, path)).hashCode();\n        } else {\n            return FileDownloadUtils.md5(formatString(\"%sp%s\", url, path)).hashCode();\n        }\n    }\n}\n"
  },
  {
    "path": "library/src/main/java/com/liulishuo/filedownloader/services/DownloadMgrInitialParams.java",
    "content": "/*\n * Copyright (c) 2015 LingoChamp Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *    http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.liulishuo.filedownloader.services;\n\nimport com.liulishuo.filedownloader.connection.DefaultConnectionCountAdapter;\nimport com.liulishuo.filedownloader.connection.FileDownloadUrlConnection;\nimport com.liulishuo.filedownloader.database.FileDownloadDatabase;\nimport com.liulishuo.filedownloader.database.RemitDatabase;\nimport com.liulishuo.filedownloader.model.FileDownloadModel;\nimport com.liulishuo.filedownloader.stream.FileDownloadOutputStream;\nimport com.liulishuo.filedownloader.stream.FileDownloadRandomAccessFile;\nimport com.liulishuo.filedownloader.util.FileDownloadHelper;\nimport com.liulishuo.filedownloader.util.FileDownloadLog;\nimport com.liulishuo.filedownloader.util.FileDownloadProperties;\nimport com.liulishuo.filedownloader.util.FileDownloadUtils;\n\n/**\n * Params in this class is used in the downloading manager, and would be used for initialize the\n * download manager in the process the downloader service settled on.\n */\npublic class DownloadMgrInitialParams {\n\n    private final InitCustomMaker mMaker;\n\n    public DownloadMgrInitialParams() {\n        mMaker = null;\n    }\n\n    public DownloadMgrInitialParams(InitCustomMaker maker) {\n        this.mMaker = maker;\n    }\n\n    public int getMaxNetworkThreadCount() {\n        if (mMaker == null) {\n            return getDefaultMaxNetworkThreadCount();\n        }\n\n        final Integer customizeMaxNetworkThreadCount = mMaker.mMaxNetworkThreadCount;\n\n        if (customizeMaxNetworkThreadCount != null) {\n            if (FileDownloadLog.NEED_LOG) {\n                FileDownloadLog.d(this, \"initial FileDownloader manager with the customize \"\n                        + \"maxNetworkThreadCount: %d\", customizeMaxNetworkThreadCount);\n            }\n\n            return FileDownloadProperties\n                    .getValidNetworkThreadCount(customizeMaxNetworkThreadCount);\n        } else {\n            return getDefaultMaxNetworkThreadCount();\n        }\n\n    }\n\n    public FileDownloadDatabase createDatabase() {\n        if (mMaker == null || mMaker.mDatabaseCustomMaker == null) {\n            return createDefaultDatabase();\n        }\n        final FileDownloadDatabase customDatabase = mMaker.mDatabaseCustomMaker.customMake();\n\n        if (customDatabase != null) {\n            if (FileDownloadLog.NEED_LOG) {\n                FileDownloadLog.d(this, \"initial FileDownloader manager with the customize \"\n                        + \"database: %s\", customDatabase);\n            }\n            return customDatabase;\n        } else {\n            return createDefaultDatabase();\n        }\n    }\n\n\n    public FileDownloadHelper.OutputStreamCreator createOutputStreamCreator() {\n        if (mMaker == null) {\n            return createDefaultOutputStreamCreator();\n        }\n\n        final FileDownloadHelper.OutputStreamCreator outputStreamCreator =\n                mMaker.mOutputStreamCreator;\n        if (outputStreamCreator != null) {\n            if (FileDownloadLog.NEED_LOG) {\n                FileDownloadLog.d(this, \"initial FileDownloader manager with the customize \"\n                        + \"output stream: %s\", outputStreamCreator);\n            }\n            return outputStreamCreator;\n        } else {\n            return createDefaultOutputStreamCreator();\n        }\n    }\n\n    public FileDownloadHelper.ConnectionCreator createConnectionCreator() {\n        if (mMaker == null) {\n            return createDefaultConnectionCreator();\n        }\n\n        final FileDownloadHelper.ConnectionCreator connectionCreator = mMaker.mConnectionCreator;\n\n        if (connectionCreator != null) {\n            if (FileDownloadLog.NEED_LOG) {\n                FileDownloadLog.d(this, \"initial FileDownloader manager with the customize \"\n                        + \"connection creator: %s\", connectionCreator);\n            }\n            return connectionCreator;\n        } else {\n            return createDefaultConnectionCreator();\n        }\n    }\n\n    public FileDownloadHelper.ConnectionCountAdapter createConnectionCountAdapter() {\n        if (mMaker == null) {\n            return createDefaultConnectionCountAdapter();\n        }\n\n        final FileDownloadHelper.ConnectionCountAdapter adapter = mMaker.mConnectionCountAdapter;\n        if (adapter != null) {\n            if (FileDownloadLog.NEED_LOG) {\n                FileDownloadLog.d(this, \"initial FileDownloader manager with the customize \"\n                        + \"connection count adapter: %s\", adapter);\n            }\n            return adapter;\n        } else {\n            return createDefaultConnectionCountAdapter();\n        }\n    }\n\n    public FileDownloadHelper.IdGenerator createIdGenerator() {\n        if (mMaker == null) {\n            return createDefaultIdGenerator();\n        }\n\n        final FileDownloadHelper.IdGenerator idGenerator = mMaker.mIdGenerator;\n        if (idGenerator != null) {\n            if (FileDownloadLog.NEED_LOG) {\n                FileDownloadLog.d(this, \"initial FileDownloader manager with the customize \"\n                        + \"id generator: %s\", idGenerator);\n            }\n\n            return idGenerator;\n        } else {\n            return createDefaultIdGenerator();\n        }\n    }\n\n    public ForegroundServiceConfig createForegroundServiceConfig() {\n        if (mMaker == null) {\n            return createDefaultForegroundServiceConfig();\n        }\n\n        final ForegroundServiceConfig foregroundServiceConfig = mMaker.mForegroundServiceConfig;\n        if (foregroundServiceConfig != null) {\n            if (FileDownloadLog.NEED_LOG) {\n                FileDownloadLog.d(this, \"initial FileDownloader manager with the customize \"\n                        + \"foreground service config: %s\", foregroundServiceConfig);\n            }\n            return foregroundServiceConfig;\n        } else {\n            return createDefaultForegroundServiceConfig();\n        }\n    }\n\n    private ForegroundServiceConfig createDefaultForegroundServiceConfig() {\n        return new ForegroundServiceConfig.Builder().needRecreateChannelId(true).build();\n    }\n\n    private FileDownloadHelper.IdGenerator createDefaultIdGenerator() {\n        return new DefaultIdGenerator();\n    }\n\n    private int getDefaultMaxNetworkThreadCount() {\n        return FileDownloadProperties.getImpl().downloadMaxNetworkThreadCount;\n    }\n\n    private FileDownloadDatabase createDefaultDatabase() {\n        return new RemitDatabase();\n    }\n\n    private FileDownloadHelper.OutputStreamCreator createDefaultOutputStreamCreator() {\n        return new FileDownloadRandomAccessFile.Creator();\n    }\n\n    private FileDownloadHelper.ConnectionCreator createDefaultConnectionCreator() {\n        return new FileDownloadUrlConnection.Creator();\n    }\n\n    private FileDownloadHelper.ConnectionCountAdapter createDefaultConnectionCountAdapter() {\n        return new DefaultConnectionCountAdapter();\n    }\n\n    public static class InitCustomMaker {\n        FileDownloadHelper.DatabaseCustomMaker mDatabaseCustomMaker;\n        Integer mMaxNetworkThreadCount;\n        FileDownloadHelper.OutputStreamCreator mOutputStreamCreator;\n        FileDownloadHelper.ConnectionCreator mConnectionCreator;\n        FileDownloadHelper.ConnectionCountAdapter mConnectionCountAdapter;\n        FileDownloadHelper.IdGenerator mIdGenerator;\n        ForegroundServiceConfig mForegroundServiceConfig;\n\n        /**\n         * customize the id generator.\n         *\n         * @param idGenerator the id generator used for generating download identify manually.\n         */\n        public InitCustomMaker idGenerator(FileDownloadHelper.IdGenerator idGenerator) {\n            this.mIdGenerator = idGenerator;\n            return this;\n        }\n\n        /**\n         * customize the connection count adapter.\n         *\n         * @param adapter the adapter used for determine how many connection will be used to\n         *                downloading the target task.\n         * @return the connection count adapter.\n         */\n        public InitCustomMaker connectionCountAdapter(\n                FileDownloadHelper.ConnectionCountAdapter adapter) {\n            this.mConnectionCountAdapter = adapter;\n            return this;\n        }\n\n        /**\n         * customize the database component.\n         * <p>\n         * If you don't customize the data component, we use the result of\n         * {@link #createDefaultDatabase()} as the default one.\n         *\n         * @param maker The database is used for storing the {@link FileDownloadModel}.\n         *              <p>\n         *              The data stored in the database is only used for task resumes from the\n         *              breakpoint.\n         *              <p>\n         *              The task of the data stored in the database must be a task that has not\n         *              finished downloading yet, and if the task has finished downloading, its data\n         *              will be {@link FileDownloadDatabase#remove(int)} from the database, since\n         *              that data is no longer available for resumption of its task pass.\n         */\n        public InitCustomMaker database(FileDownloadHelper.DatabaseCustomMaker maker) {\n            this.mDatabaseCustomMaker = maker;\n            return this;\n        }\n\n        /**\n         * Customize the max network thread count.\n         * <p>\n         * If you don't customize the network thread count, we use the result of\n         * {@link #getDefaultMaxNetworkThreadCount()} as the default one.\n         *\n         * @param maxNetworkThreadCount The maximum count of the network thread, what is the number\n         *                              of simultaneous downloads in FileDownloader.\n         *                              <p>\n         *                              If this value is less than or equal to 0, the value will be\n         *                              ignored and use\n         *                              {@link FileDownloadProperties#downloadMaxNetworkThreadCount}\n         *                              which is defined in filedownloader.properties instead.\n         */\n        public InitCustomMaker maxNetworkThreadCount(int maxNetworkThreadCount) {\n            if (maxNetworkThreadCount > 0) {\n                this.mMaxNetworkThreadCount = maxNetworkThreadCount;\n            }\n            return this;\n        }\n\n        /**\n         * Customize the output stream component.\n         * <p>\n         * If you don't customize the output stream component, we use the result of\n         * {@link #createDefaultOutputStreamCreator()} as the default one.\n         *\n         * @param creator The output stream creator is used for creating\n         *                {@link FileDownloadOutputStream} which is used to write the input stream\n         *                to the file for downloading.\n         */\n        public InitCustomMaker outputStreamCreator(FileDownloadHelper.OutputStreamCreator creator) {\n            this.mOutputStreamCreator = creator;\n            if (mOutputStreamCreator != null && !mOutputStreamCreator.supportSeek()) {\n                if (!FileDownloadProperties.getImpl().fileNonPreAllocation) {\n                    throw new IllegalArgumentException(\n                            \"Since the provided FileDownloadOutputStream \"\n                                    + \"does not support the seek function, if FileDownloader\"\n                                    + \" pre-allocates file size at the beginning of the download,\"\n                                    + \" it will can not be resumed from the breakpoint. If you need\"\n                                    + \" to ensure that the resumption is available, please add and\"\n                                    + \" set the value of 'file.non-pre-allocation' field to 'true'\"\n                                    + \" in the 'filedownloader.properties' file which is in your\"\n                                    + \" application assets folder manually for resolving this \"\n                                    + \"problem.\");\n                }\n            }\n            return this;\n        }\n\n        /**\n         * Customize the connection component.\n         * <p>\n         * If you don't customize the connection component, we use the result of\n         * {@link #createDefaultConnectionCreator()} as the default one.\n         *\n         * @param creator the connection creator will used for create the connection when start\n         *                downloading any task in the FileDownloader.\n         */\n        public InitCustomMaker connectionCreator(FileDownloadHelper.ConnectionCreator creator) {\n            this.mConnectionCreator = creator;\n            return this;\n        }\n\n        /**\n         * customize configurations of foreground service\n         * @param config determines how to show an notification for the foreground service\n         */\n        public InitCustomMaker foregroundServiceConfig(ForegroundServiceConfig config) {\n            this.mForegroundServiceConfig = config;\n            return this;\n        }\n\n        @SuppressWarnings(\"EmptyMethod\")\n        public void commit() {\n            // do nothing now.\n        }\n\n        @Override\n        public String toString() {\n            return FileDownloadUtils.formatString(\"component: database[%s], maxNetworkCount[%s],\"\n                            + \" outputStream[%s], connection[%s], connectionCountAdapter[%s]\",\n                    mDatabaseCustomMaker, mMaxNetworkThreadCount, mOutputStreamCreator,\n                    mConnectionCreator, mConnectionCountAdapter);\n        }\n    }\n}\n"
  },
  {
    "path": "library/src/main/java/com/liulishuo/filedownloader/services/FDServiceSeparateHandler.java",
    "content": "/*\n * Copyright (c) 2015 LingoChamp Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *    http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage com.liulishuo.filedownloader.services;\n\nimport android.app.Notification;\nimport android.content.Intent;\nimport android.os.IBinder;\nimport android.os.RemoteCallbackList;\nimport android.os.RemoteException;\n\nimport com.liulishuo.filedownloader.i.IFileDownloadIPCCallback;\nimport com.liulishuo.filedownloader.i.IFileDownloadIPCService;\nimport com.liulishuo.filedownloader.message.MessageSnapshot;\nimport com.liulishuo.filedownloader.message.MessageSnapshotFlow;\nimport com.liulishuo.filedownloader.model.FileDownloadHeader;\nimport com.liulishuo.filedownloader.util.FileDownloadLog;\n\nimport java.lang.ref.WeakReference;\n\n/**\n * For handling the case of the FileDownloadService runs in separate `:filedownloader` process.\n */\npublic class FDServiceSeparateHandler extends IFileDownloadIPCService.Stub\n        implements MessageSnapshotFlow.MessageReceiver, IFileDownloadServiceHandler {\n\n    private final RemoteCallbackList<IFileDownloadIPCCallback> callbackList =\n            new RemoteCallbackList<>();\n    private final FileDownloadManager downloadManager;\n    private final WeakReference<FileDownloadService> wService;\n\n    @SuppressWarnings(\"UnusedReturnValue\")\n    private synchronized int callback(MessageSnapshot snapShot) {\n        final int n = callbackList.beginBroadcast();\n        try {\n            for (int i = 0; i < n; i++) {\n                callbackList.getBroadcastItem(i).callback(snapShot);\n            }\n        } catch (RemoteException e) {\n            FileDownloadLog.e(this, e, \"callback error\");\n        } finally {\n            callbackList.finishBroadcast();\n        }\n\n        return n;\n    }\n\n    FDServiceSeparateHandler(WeakReference<FileDownloadService> wService,\n                             FileDownloadManager manager) {\n        this.wService = wService;\n        this.downloadManager = manager;\n\n        MessageSnapshotFlow.getImpl().setReceiver(this);\n    }\n\n    @Override\n    public void registerCallback(IFileDownloadIPCCallback callback) throws RemoteException {\n        callbackList.register(callback);\n    }\n\n    @Override\n    public void unregisterCallback(IFileDownloadIPCCallback callback) throws RemoteException {\n        callbackList.unregister(callback);\n    }\n\n    @Override\n    public boolean checkDownloading(String url, String path) throws RemoteException {\n        return downloadManager.isDownloading(url, path);\n    }\n\n    @Override\n    public void start(String url, String path, boolean pathAsDirectory, int callbackProgressTimes,\n                      int callbackProgressMinIntervalMillis, int autoRetryTimes,\n                      boolean forceReDownload,\n                      FileDownloadHeader header, boolean isWifiRequired) throws RemoteException {\n        downloadManager.start(url, path, pathAsDirectory, callbackProgressTimes,\n                callbackProgressMinIntervalMillis, autoRetryTimes, forceReDownload, header,\n                isWifiRequired);\n    }\n\n    @Override\n    public boolean pause(int downloadId) throws RemoteException {\n        return downloadManager.pause(downloadId);\n    }\n\n    @Override\n    public void pauseAllTasks() throws RemoteException {\n        downloadManager.pauseAll();\n    }\n\n    @Override\n    public boolean setMaxNetworkThreadCount(int count) throws RemoteException {\n        return downloadManager.setMaxNetworkThreadCount(count);\n    }\n\n    @Override\n    public long getSofar(int downloadId) throws RemoteException {\n        return downloadManager.getSoFar(downloadId);\n    }\n\n    @Override\n    public long getTotal(int downloadId) throws RemoteException {\n        return downloadManager.getTotal(downloadId);\n    }\n\n    @Override\n    public byte getStatus(int downloadId) throws RemoteException {\n        return downloadManager.getStatus(downloadId);\n    }\n\n    @Override\n    public boolean isIdle() throws RemoteException {\n        return downloadManager.isIdle();\n    }\n\n    @Override\n    public void startForeground(int id, Notification notification) throws RemoteException {\n        if (this.wService != null && this.wService.get() != null) {\n            this.wService.get().startForeground(id, notification);\n        }\n    }\n\n    @Override\n    public void stopForeground(boolean removeNotification) throws RemoteException {\n        if (this.wService != null && this.wService.get() != null) {\n            this.wService.get().stopForeground(removeNotification);\n        }\n    }\n\n    @Override\n    public boolean clearTaskData(int id) throws RemoteException {\n        return downloadManager.clearTaskData(id);\n    }\n\n    @Override\n    public void clearAllTaskData() throws RemoteException {\n        downloadManager.clearAllTaskData();\n    }\n\n    @Override\n    public void onStartCommand(Intent intent, int flags, int startId) {\n    }\n\n    @Override\n    public IBinder onBind(Intent intent) {\n        return this;\n    }\n\n    @Override\n    public void onDestroy() {\n        MessageSnapshotFlow.getImpl().setReceiver(null);\n    }\n\n    @Override\n    public void receive(MessageSnapshot snapShot) {\n        callback(snapShot);\n    }\n}\n"
  },
  {
    "path": "library/src/main/java/com/liulishuo/filedownloader/services/FDServiceSharedHandler.java",
    "content": "/*\n * Copyright (c) 2015 LingoChamp Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *    http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage com.liulishuo.filedownloader.services;\n\nimport android.app.Notification;\nimport android.content.Intent;\nimport android.os.IBinder;\n\nimport com.liulishuo.filedownloader.FileDownloadServiceProxy;\nimport com.liulishuo.filedownloader.i.IFileDownloadIPCCallback;\nimport com.liulishuo.filedownloader.i.IFileDownloadIPCService;\nimport com.liulishuo.filedownloader.model.FileDownloadHeader;\n\nimport java.lang.ref.WeakReference;\n\n/**\n * For handling the case of the FileDownloadService runs in shared the main process.\n */\npublic class FDServiceSharedHandler extends IFileDownloadIPCService.Stub\n        implements IFileDownloadServiceHandler {\n    private final FileDownloadManager downloadManager;\n    private final WeakReference<FileDownloadService> wService;\n\n    FDServiceSharedHandler(WeakReference<FileDownloadService> wService,\n                           FileDownloadManager manager) {\n        this.wService = wService;\n        this.downloadManager = manager;\n    }\n\n    @Override\n    public void registerCallback(IFileDownloadIPCCallback callback) {\n    }\n\n    @Override\n    public void unregisterCallback(IFileDownloadIPCCallback callback) {\n    }\n\n    @Override\n    public boolean checkDownloading(String url, String path) {\n        return downloadManager.isDownloading(url, path);\n    }\n\n    @Override\n    public void start(String url, String path, boolean pathAsDirectory, int callbackProgressTimes,\n                      int callbackProgressMinIntervalMillis, int autoRetryTimes,\n                      boolean forceReDownload,\n                      FileDownloadHeader header, boolean isWifiRequired) {\n        downloadManager.start(url, path, pathAsDirectory, callbackProgressTimes,\n                callbackProgressMinIntervalMillis, autoRetryTimes, forceReDownload, header,\n                isWifiRequired);\n    }\n\n    @Override\n    public boolean pause(int downloadId) {\n        return downloadManager.pause(downloadId);\n    }\n\n    @Override\n    public void pauseAllTasks() {\n        downloadManager.pauseAll();\n    }\n\n    @Override\n    public boolean setMaxNetworkThreadCount(int count) {\n        return downloadManager.setMaxNetworkThreadCount(count);\n    }\n\n    @Override\n    public long getSofar(int downloadId) {\n        return downloadManager.getSoFar(downloadId);\n    }\n\n    @Override\n    public long getTotal(int downloadId) {\n        return downloadManager.getTotal(downloadId);\n    }\n\n    @Override\n    public byte getStatus(int downloadId) {\n        return downloadManager.getStatus(downloadId);\n    }\n\n    @Override\n    public boolean isIdle() {\n        return downloadManager.isIdle();\n    }\n\n    @Override\n    public void startForeground(int id, Notification notification) {\n        if (this.wService != null && this.wService.get() != null) {\n            this.wService.get().startForeground(id, notification);\n        }\n    }\n\n    @Override\n    public void stopForeground(boolean removeNotification) {\n        if (this.wService != null && this.wService.get() != null) {\n            this.wService.get().stopForeground(removeNotification);\n        }\n    }\n\n    @Override\n    public boolean clearTaskData(int id) {\n        return downloadManager.clearTaskData(id);\n    }\n\n    @Override\n    public void clearAllTaskData() {\n        downloadManager.clearAllTaskData();\n    }\n\n    @Override\n    public void onStartCommand(Intent intent, int flags, int startId) {\n        //noinspection ConstantConditions\n        FileDownloadServiceProxy.getConnectionListener().onConnected(this);\n    }\n\n    @Override\n    public IBinder onBind(Intent intent) {\n        return null;\n    }\n\n    @Override\n    public void onDestroy() {\n        //noinspection ConstantConditions\n        FileDownloadServiceProxy.getConnectionListener().onDisconnected();\n    }\n\n    public interface FileDownloadServiceSharedConnection {\n        void onConnected(FDServiceSharedHandler handler);\n\n        void onDisconnected();\n    }\n}\n"
  },
  {
    "path": "library/src/main/java/com/liulishuo/filedownloader/services/FileDownloadBroadcastHandler.java",
    "content": "/*\n * Copyright (c) 2015 LingoChamp Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *   http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.liulishuo.filedownloader.services;\n\nimport android.content.Intent;\n\nimport com.liulishuo.filedownloader.model.FileDownloadModel;\nimport com.liulishuo.filedownloader.model.FileDownloadStatus;\nimport com.liulishuo.filedownloader.util.FileDownloadHelper;\nimport com.liulishuo.filedownloader.util.FileDownloadUtils;\n\n/**\n * The handler broadcast from filedownloader.\n */\npublic class FileDownloadBroadcastHandler {\n    public static final String ACTION_COMPLETED = \"filedownloader.intent.action.completed\";\n    public static final String KEY_MODEL = \"model\";\n\n    /**\n     * Parse the {@code intent} from the filedownloader broadcast.\n     *\n     * @param intent the intent from the broadcast.\n     * @return the file download model.\n     */\n    public static FileDownloadModel parseIntent(Intent intent) {\n        if (!ACTION_COMPLETED.equals(intent.getAction())) {\n            throw new IllegalArgumentException(FileDownloadUtils.\n                    formatString(\"can't recognize the intent with action %s, on the current\"\n                            + \" version we only support action [%s]\",\n                            intent.getAction(), ACTION_COMPLETED));\n        }\n\n        return intent.getParcelableExtra(KEY_MODEL);\n    }\n\n    public static void sendCompletedBroadcast(FileDownloadModel model) {\n        if (model == null) throw new IllegalArgumentException();\n        if (model.getStatus() != FileDownloadStatus.completed) throw new IllegalStateException();\n\n        final Intent intent = new Intent(ACTION_COMPLETED);\n        intent.putExtra(KEY_MODEL, model);\n\n        FileDownloadHelper.getAppContext().sendBroadcast(intent);\n    }\n}\n"
  },
  {
    "path": "library/src/main/java/com/liulishuo/filedownloader/services/FileDownloadManager.java",
    "content": "/*\n * Copyright (c) 2015 LingoChamp Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *    http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.liulishuo.filedownloader.services;\n\n\nimport android.text.TextUtils;\n\nimport com.liulishuo.filedownloader.IThreadPoolMonitor;\nimport com.liulishuo.filedownloader.PauseAllMarker;\nimport com.liulishuo.filedownloader.database.FileDownloadDatabase;\nimport com.liulishuo.filedownloader.download.CustomComponentHolder;\nimport com.liulishuo.filedownloader.download.DownloadLaunchRunnable;\nimport com.liulishuo.filedownloader.download.DownloadRunnable;\nimport com.liulishuo.filedownloader.model.ConnectionModel;\nimport com.liulishuo.filedownloader.model.FileDownloadHeader;\nimport com.liulishuo.filedownloader.model.FileDownloadModel;\nimport com.liulishuo.filedownloader.model.FileDownloadStatus;\nimport com.liulishuo.filedownloader.util.FileDownloadHelper;\nimport com.liulishuo.filedownloader.util.FileDownloadLog;\nimport com.liulishuo.filedownloader.util.FileDownloadUtils;\n\nimport java.util.List;\n\n/**\n * The downloading manager in FileDownloadService, which is used to control all download-inflow.\n * <p/>\n * Handling real {@link #start(String, String, boolean, int, int, int, boolean, FileDownloadHeader,\n * boolean)}.\n *\n * @see FileDownloadThreadPool\n * @see DownloadLaunchRunnable\n * @see DownloadRunnable\n */\nclass FileDownloadManager implements IThreadPoolMonitor {\n    private final FileDownloadDatabase mDatabase;\n    private final FileDownloadThreadPool mThreadPool;\n\n    FileDownloadManager() {\n        final CustomComponentHolder holder = CustomComponentHolder.getImpl();\n        this.mDatabase = holder.getDatabaseInstance();\n        this.mThreadPool = new FileDownloadThreadPool(holder.getMaxNetworkThreadCount());\n    }\n\n    // synchronize for safe: check downloading, check resume, update data, execute runnable\n    public synchronized void start(final String url, final String path,\n                                   final boolean pathAsDirectory,\n                                   final int callbackProgressTimes,\n                                   final int callbackProgressMinIntervalMillis,\n                                   final int autoRetryTimes, final boolean forceReDownload,\n                                   final FileDownloadHeader header, final boolean isWifiRequired) {\n        if (FileDownloadLog.NEED_LOG) {\n            FileDownloadLog.d(this, \"request start the task with url(%s) path(%s) isDirectory(%B)\",\n                    url, path, pathAsDirectory);\n        }\n\n        // clear pause all marker so that the delay pause all check doesn't obstruct normal start of\n        // new task\n        PauseAllMarker.clearMarker();\n\n        final int id = FileDownloadUtils.generateId(url, path, pathAsDirectory);\n        FileDownloadModel model = mDatabase.find(id);\n\n        List<ConnectionModel> dirConnectionModelList = null;\n\n        if (!pathAsDirectory && model == null) {\n            // try dir data.\n            final int dirCaseId = FileDownloadUtils\n                    .generateId(url, FileDownloadUtils.getParent(path),\n                            true);\n            model = mDatabase.find(dirCaseId);\n            if (model != null && path.equals(model.getTargetFilePath())) {\n                if (FileDownloadLog.NEED_LOG) {\n                    FileDownloadLog.d(this, \"task[%d] find model by dirCaseId[%d]\", id, dirCaseId);\n                }\n\n                dirConnectionModelList = mDatabase.findConnectionModel(dirCaseId);\n            }\n        }\n\n        if (FileDownloadHelper.inspectAndInflowDownloading(id, model, this, true)) {\n            if (FileDownloadLog.NEED_LOG) {\n                FileDownloadLog.d(this, \"has already started download %d\", id);\n            }\n            return;\n        }\n\n        final String targetFilePath = model != null ? model.getTargetFilePath()\n                : FileDownloadUtils.getTargetFilePath(path, pathAsDirectory, null);\n        if (FileDownloadHelper.inspectAndInflowDownloaded(id, targetFilePath, forceReDownload,\n                true)) {\n            if (FileDownloadLog.NEED_LOG) {\n                FileDownloadLog.d(this, \"has already completed downloading %d\", id);\n            }\n            return;\n        }\n\n        final long sofar = model != null ? model.getSoFar() : 0;\n        final String tempFilePath = model != null ? model.getTempFilePath()\n                : FileDownloadUtils.getTempPath(targetFilePath);\n        if (FileDownloadHelper.inspectAndInflowConflictPath(id, sofar, tempFilePath, targetFilePath,\n                this)) {\n            if (FileDownloadLog.NEED_LOG) {\n                FileDownloadLog\n                        .d(this, \"there is an another task with the same target-file-path %d %s\",\n                                id, targetFilePath);\n            }\n\n            // because of the file is dirty for this task.\n            if (model != null) {\n                mDatabase.remove(id);\n                mDatabase.removeConnections(id);\n            }\n            return;\n        }\n\n        // real start\n        // - create model\n        boolean needUpdate2DB;\n        if (model != null\n                && (model.getStatus() == FileDownloadStatus.paused\n                || model.getStatus() == FileDownloadStatus.error\n                || model.getStatus() == FileDownloadStatus.pending\n                || model.getStatus() == FileDownloadStatus.started\n                || model.getStatus() == FileDownloadStatus.connected) // FileDownloadRunnable\n            // invoke #isBreakpointAvailable to determine whether it is really invalid.\n                ) {\n            if (model.getId() != id) {\n                // in try dir case.\n                mDatabase.remove(model.getId());\n                mDatabase.removeConnections(model.getId());\n\n                model.setId(id);\n                model.setPath(path, pathAsDirectory);\n                if (dirConnectionModelList != null) {\n                    for (ConnectionModel connectionModel : dirConnectionModelList) {\n                        connectionModel.setId(id);\n                        mDatabase.insertConnectionModel(connectionModel);\n                    }\n                }\n\n                needUpdate2DB = true;\n            } else {\n                if (!TextUtils.equals(url, model.getUrl())) {\n                    // for cover the case of reusing the downloaded processing with the different\n                    // url( using with idGenerator ).\n                    model.setUrl(url);\n                    needUpdate2DB = true;\n                } else {\n                    needUpdate2DB = false;\n                }\n            }\n        } else {\n            if (model == null) {\n                model = new FileDownloadModel();\n            }\n            model.setUrl(url);\n            model.setPath(path, pathAsDirectory);\n\n            model.setId(id);\n            model.setSoFar(0);\n            model.setTotal(0);\n            model.setStatus(FileDownloadStatus.pending);\n            model.setConnectionCount(1);\n            needUpdate2DB = true;\n        }\n\n        // - update model to db\n        if (needUpdate2DB) {\n            mDatabase.update(model);\n        }\n\n        final DownloadLaunchRunnable.Builder builder = new DownloadLaunchRunnable.Builder();\n\n        final DownloadLaunchRunnable runnable =\n                builder.setModel(model)\n                        .setHeader(header)\n                        .setThreadPoolMonitor(this)\n                        .setMinIntervalMillis(callbackProgressMinIntervalMillis)\n                        .setCallbackProgressMaxCount(callbackProgressTimes)\n                        .setForceReDownload(forceReDownload)\n                        .setWifiRequired(isWifiRequired)\n                        .setMaxRetryTimes(autoRetryTimes)\n                        .build();\n\n        // - execute\n        mThreadPool.execute(runnable);\n\n    }\n\n    public boolean isDownloading(String url, String path) {\n        return isDownloading(FileDownloadUtils.generateId(url, path));\n    }\n\n    public boolean isDownloading(int id) {\n        return isDownloading(mDatabase.find(id));\n    }\n\n    public boolean pause(final int id) {\n        if (FileDownloadLog.NEED_LOG) {\n            FileDownloadLog.d(this, \"request pause the task %d\", id);\n        }\n\n        final FileDownloadModel model = mDatabase.find(id);\n        if (model == null) {\n            return false;\n        }\n\n        model.setStatus(FileDownloadStatus.paused);\n        mThreadPool.cancel(id);\n        return true;\n    }\n\n    /**\n     * Pause all running task\n     */\n    public void pauseAll() {\n        List<Integer> list = mThreadPool.getAllExactRunningDownloadIds();\n\n        if (FileDownloadLog.NEED_LOG) {\n            FileDownloadLog.d(this, \"pause all tasks %d\", list.size());\n        }\n\n        for (Integer id : list) {\n            pause(id);\n        }\n    }\n\n    public long getSoFar(final int id) {\n        final FileDownloadModel model = mDatabase.find(id);\n        if (model == null) {\n            return 0;\n        }\n\n        final int connectionCount = model.getConnectionCount();\n        if (connectionCount <= 1) {\n            return model.getSoFar();\n        } else {\n            final List<ConnectionModel> modelList = mDatabase.findConnectionModel(id);\n            if (modelList == null || modelList.size() != connectionCount) {\n                return 0;\n            } else {\n                return ConnectionModel.getTotalOffset(modelList);\n            }\n        }\n    }\n\n    public long getTotal(final int id) {\n        final FileDownloadModel model = mDatabase.find(id);\n        if (model == null) {\n            return 0;\n        }\n\n        return model.getTotal();\n    }\n\n    public byte getStatus(final int id) {\n        final FileDownloadModel model = mDatabase.find(id);\n        if (model == null) {\n            return FileDownloadStatus.INVALID_STATUS;\n        }\n\n        return model.getStatus();\n    }\n\n    public boolean isIdle() {\n        return mThreadPool.exactSize() <= 0;\n    }\n\n    public synchronized boolean setMaxNetworkThreadCount(int count) {\n        return mThreadPool.setMaxNetworkThreadCount(count);\n    }\n\n    @Override\n    public boolean isDownloading(FileDownloadModel model) {\n        if (model == null) {\n            return false;\n        }\n\n        final boolean isInPool = mThreadPool.isInThreadPool(model.getId());\n        boolean isDownloading;\n\n        do {\n            if (FileDownloadStatus.isOver(model.getStatus())) {\n\n                //noinspection RedundantIfStatement\n                if (isInPool) {\n                    // already finished, but still in the pool.\n                    // handle as downloading.\n                    isDownloading = true;\n                } else {\n                    // already finished, and not in the pool.\n                    // make sense.\n                    isDownloading = false;\n\n                }\n            } else {\n                if (isInPool) {\n                    // not finish, in the pool.\n                    // make sense.\n                    isDownloading = true;\n                } else {\n                    // not finish, but not in the pool.\n                    // beyond expectation.\n                    FileDownloadLog.e(this, \"%d status is[%s](not finish) & but not in the pool\",\n                            model.getId(), model.getStatus());\n                    // handle as not in downloading, going to re-downloading.\n                    isDownloading = false;\n\n                }\n            }\n        } while (false);\n\n        return isDownloading;\n    }\n\n    @Override\n    public int findRunningTaskIdBySameTempPath(String tempFilePath, int excludeId) {\n        return mThreadPool.findRunningTaskIdBySameTempPath(tempFilePath, excludeId);\n    }\n\n    public boolean clearTaskData(int id) {\n        if (id == 0) {\n            FileDownloadLog.w(this, \"The task[%d] id is invalid, can't clear it.\", id);\n            return false;\n        }\n\n        if (isDownloading(id)) {\n            FileDownloadLog.w(this, \"The task[%d] is downloading, can't clear it.\", id);\n            return false;\n        }\n\n        mDatabase.remove(id);\n        mDatabase.removeConnections(id);\n        return true;\n    }\n\n    public void clearAllTaskData() {\n        mDatabase.clear();\n    }\n}\n\n"
  },
  {
    "path": "library/src/main/java/com/liulishuo/filedownloader/services/FileDownloadService.java",
    "content": "/*\n * Copyright (c) 2015 LingoChamp Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *    http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.liulishuo.filedownloader.services;\n\nimport android.annotation.SuppressLint;\nimport android.app.NotificationChannel;\nimport android.app.NotificationManager;\nimport android.app.Service;\nimport android.content.Context;\nimport android.content.Intent;\nimport android.os.Build;\nimport android.os.IBinder;\n\nimport com.liulishuo.filedownloader.PauseAllMarker;\nimport com.liulishuo.filedownloader.download.CustomComponentHolder;\nimport com.liulishuo.filedownloader.i.IFileDownloadIPCService;\nimport com.liulishuo.filedownloader.util.ExtraKeys;\nimport com.liulishuo.filedownloader.util.FileDownloadHelper;\nimport com.liulishuo.filedownloader.util.FileDownloadLog;\nimport com.liulishuo.filedownloader.util.FileDownloadProperties;\nimport com.liulishuo.filedownloader.util.FileDownloadUtils;\n\nimport java.lang.ref.WeakReference;\n\n/**\n * The service is running for FileDownloader.\n * <p/>\n * You can add a command `process.non-separate=true` to the `filedownloader.properties` asset file\n * to make the FileDownloadService runs in the main process, and by default the FileDownloadService\n * runs in the separate process(`:filedownloader`).\n */\n@SuppressLint(\"Registered\")\npublic class FileDownloadService extends Service {\n\n    private IFileDownloadServiceHandler handler;\n    private PauseAllMarker pauseAllMarker;\n\n    @Override\n    public void onCreate() {\n        super.onCreate();\n        FileDownloadHelper.holdContext(this);\n\n        try {\n            FileDownloadUtils.setMinProgressStep(\n                    FileDownloadProperties.getImpl().downloadMinProgressStep);\n            FileDownloadUtils.setMinProgressTime(\n                    FileDownloadProperties.getImpl().downloadMinProgressTime);\n        } catch (IllegalAccessException e) {\n            e.printStackTrace();\n        }\n\n        final FileDownloadManager manager = new FileDownloadManager();\n\n        if (FileDownloadProperties.getImpl().processNonSeparate) {\n            handler = new FDServiceSharedHandler(new WeakReference<>(this), manager);\n        } else {\n            handler = new FDServiceSeparateHandler(new WeakReference<>(this), manager);\n        }\n\n        PauseAllMarker.clearMarker();\n        pauseAllMarker = new PauseAllMarker((IFileDownloadIPCService) handler);\n        pauseAllMarker.startPauseAllLooperCheck();\n    }\n\n    @Override\n    public int onStartCommand(Intent intent, int flags, int startId) {\n        handler.onStartCommand(intent, flags, startId);\n        inspectRunServiceForeground(intent);\n        return START_STICKY;\n    }\n\n    private void inspectRunServiceForeground(Intent intent) {\n        if (intent == null) return;\n        final boolean isForeground = intent.getBooleanExtra(ExtraKeys.IS_FOREGROUND, false);\n        if (isForeground) {\n            ForegroundServiceConfig config = CustomComponentHolder.getImpl()\n                    .getForegroundConfigInstance();\n            if (config.isNeedRecreateChannelId()\n                    && Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {\n                NotificationChannel notificationChannel = new NotificationChannel(\n                        config.getNotificationChannelId(),\n                        config.getNotificationChannelName(),\n                        NotificationManager.IMPORTANCE_LOW\n                );\n                NotificationManager notificationManager =\n                        (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);\n                if (notificationManager == null) return;\n                notificationManager.createNotificationChannel(notificationChannel);\n            }\n            startForeground(config.getNotificationId(), config.getNotification(this));\n            if (FileDownloadLog.NEED_LOG) {\n                FileDownloadLog.d(this, \"run service foreground with config: %s\", config);\n            }\n        }\n    }\n\n    @Override\n    public void onDestroy() {\n        pauseAllMarker.stopPauseAllLooperCheck();\n        stopForeground(true);\n        super.onDestroy();\n    }\n\n    @Override\n    public IBinder onBind(Intent intent) {\n        return handler.onBind(intent);\n    }\n\n    public static class SharedMainProcessService extends FileDownloadService {\n    }\n\n    public static class SeparateProcessService extends FileDownloadService {\n    }\n}\n"
  },
  {
    "path": "library/src/main/java/com/liulishuo/filedownloader/services/FileDownloadThreadPool.java",
    "content": "/*\n * Copyright (c) 2015 LingoChamp Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *    http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.liulishuo.filedownloader.services;\n\nimport android.util.SparseArray;\n\nimport com.liulishuo.filedownloader.download.DownloadLaunchRunnable;\nimport com.liulishuo.filedownloader.util.FileDownloadExecutors;\nimport com.liulishuo.filedownloader.util.FileDownloadLog;\nimport com.liulishuo.filedownloader.util.FileDownloadProperties;\n\nimport java.util.ArrayList;\nimport java.util.List;\nimport java.util.concurrent.ThreadPoolExecutor;\n\n/**\n * The thread pool for driving the downloading runnable, which real access the network.\n */\nclass FileDownloadThreadPool {\n\n    private SparseArray<DownloadLaunchRunnable> runnablePool = new SparseArray<>();\n\n    private ThreadPoolExecutor mThreadPool;\n\n    private final String threadPrefix = \"Network\";\n    private int mMaxThreadCount;\n\n    FileDownloadThreadPool(final int maxNetworkThreadCount) {\n        mThreadPool = FileDownloadExecutors.newDefaultThreadPool(maxNetworkThreadCount,\n                threadPrefix);\n        mMaxThreadCount = maxNetworkThreadCount;\n    }\n\n    public synchronized boolean setMaxNetworkThreadCount(int count) {\n        if (exactSize() > 0) {\n            FileDownloadLog.w(this, \"Can't change the max network thread count, because the \"\n                    + \" network thread pool isn't in IDLE, please try again after all running\"\n                    + \" tasks are completed or invoking FileDownloader#pauseAll directly.\");\n            return false;\n        }\n\n        final int validCount = FileDownloadProperties.getValidNetworkThreadCount(count);\n\n        if (FileDownloadLog.NEED_LOG) {\n            FileDownloadLog.d(this, \"change the max network thread count, from %d to %d\",\n                    mMaxThreadCount, validCount);\n        }\n\n        final List<Runnable> taskQueue = mThreadPool.shutdownNow();\n        mThreadPool = FileDownloadExecutors.newDefaultThreadPool(validCount, threadPrefix);\n\n        if (taskQueue.size() > 0) {\n            FileDownloadLog.w(this, \"recreate the network thread pool and discard %d tasks\",\n                    taskQueue.size());\n        }\n\n        mMaxThreadCount = validCount;\n        return true;\n    }\n\n    public void execute(DownloadLaunchRunnable launchRunnable) {\n        launchRunnable.pending();\n        synchronized (this) {\n            runnablePool.put(launchRunnable.getId(), launchRunnable);\n        }\n        mThreadPool.execute(launchRunnable);\n\n        final int checkThresholdValue = 600;\n        if (mIgnoreCheckTimes >= checkThresholdValue) {\n            filterOutNoExist();\n            mIgnoreCheckTimes = 0;\n        } else {\n            mIgnoreCheckTimes++;\n        }\n    }\n\n    public void cancel(final int id) {\n        filterOutNoExist();\n        synchronized (this) {\n            DownloadLaunchRunnable r = runnablePool.get(id);\n            if (r != null) {\n                r.pause();\n                boolean result = mThreadPool.remove(r);\n                if (FileDownloadLog.NEED_LOG) {\n                    // If {@code result} is false, must be: the Runnable has been running before\n                    // invoke this method.\n                    FileDownloadLog.d(this, \"successful cancel %d %B\", id, result);\n                }\n            }\n            runnablePool.remove(id);\n        }\n    }\n\n\n    private int mIgnoreCheckTimes = 0;\n\n    private synchronized void filterOutNoExist() {\n        SparseArray<DownloadLaunchRunnable> correctedRunnablePool = new SparseArray<>();\n        final int size = runnablePool.size();\n        for (int i = 0; i < size; i++) {\n            final int key = runnablePool.keyAt(i);\n            final DownloadLaunchRunnable runnable = runnablePool.get(key);\n            if (runnable != null && runnable.isAlive()) {\n                correctedRunnablePool.put(key, runnable);\n            }\n        }\n        runnablePool = correctedRunnablePool;\n    }\n\n    public synchronized boolean isInThreadPool(final int downloadId) {\n        final DownloadLaunchRunnable runnable = runnablePool.get(downloadId);\n        return runnable != null && runnable.isAlive();\n    }\n\n    public synchronized int findRunningTaskIdBySameTempPath(String tempFilePath, int excludeId) {\n        if (null == tempFilePath) {\n            return 0;\n        }\n\n        final int size = runnablePool.size();\n        for (int i = 0; i < size; i++) {\n            final DownloadLaunchRunnable runnable = runnablePool.valueAt(i);\n            // why not clone, no out-of-bounds exception? -- yes, we dig into SparseArray and find\n            // out there are only two ways can change mValues: GrowingArrayUtils#insert and\n            // GrowingArrayUtils#append they all only grow size, and valueAt only get value on\n            // mValues.\n\n            // update(rth): No, out-of-bounds exception may occur in concurrent case. Even though\n            // mValues will always increase it's size, but this is not visible to all threads\n            // immediately. And in other hand, if the i(th) is removed, the value in mValues will be\n            // an object(SparseArray#DELETE), so, valueAt(int) method will throw ClassCastException.\n            if (runnable == null) {\n                // why it is possible to occur null on here, because the value on  runnablePool can\n                // be remove on #cancel method.\n                continue;\n            }\n\n            if (runnable.isAlive() && runnable.getId() != excludeId\n                    && tempFilePath.equals(runnable.getTempFilePath())) {\n                return runnable.getId();\n            }\n        }\n\n        return 0;\n    }\n\n    public synchronized int exactSize() {\n        filterOutNoExist();\n        return runnablePool.size();\n    }\n\n    public synchronized List<Integer> getAllExactRunningDownloadIds() {\n        filterOutNoExist();\n\n        List<Integer> list = new ArrayList<>();\n        for (int i = 0; i < runnablePool.size(); i++) {\n            list.add(runnablePool.get(runnablePool.keyAt(i)).getId());\n        }\n\n        return list;\n    }\n}\n"
  },
  {
    "path": "library/src/main/java/com/liulishuo/filedownloader/services/ForegroundServiceConfig.java",
    "content": "/*\n * Copyright (c) 2015 LingoChamp Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *   http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.liulishuo.filedownloader.services;\n\nimport android.annotation.TargetApi;\nimport android.app.Notification;\nimport android.content.Context;\n\nimport com.liulishuo.filedownloader.R;\nimport com.liulishuo.filedownloader.util.FileDownloadLog;\n\n@TargetApi(26)\npublic class ForegroundServiceConfig {\n    private int notificationId;\n    private String notificationChannelId;\n    private String notificationChannelName;\n    private Notification notification;\n    private boolean needRecreateChannelId;\n\n    private ForegroundServiceConfig() {\n    }\n\n    private static final String DEFAULT_NOTIFICATION_CHANNEL_ID = \"filedownloader_channel\";\n    private static final String DEFAULT_NOTIFICATION_CHANNEL_NAME = \"Filedownloader\";\n    private static final int DEFAULT_NOTIFICATION_ID = android.R.drawable.arrow_down_float;\n\n    public int getNotificationId() {\n        return notificationId;\n    }\n\n    public String getNotificationChannelId() {\n        return notificationChannelId;\n    }\n\n    public String getNotificationChannelName() {\n        return notificationChannelName;\n    }\n\n    public Notification getNotification(Context context) {\n        if (notification == null) {\n            if (FileDownloadLog.NEED_LOG) {\n                FileDownloadLog.d(this, \"build default notification\");\n            }\n            notification = buildDefaultNotification(context);\n        }\n        return notification;\n    }\n\n    public boolean isNeedRecreateChannelId() {\n        return needRecreateChannelId;\n    }\n\n    public void setNotificationId(int notificationId) {\n        this.notificationId = notificationId;\n    }\n\n    public void setNotificationChannelId(String notificationChannelId) {\n        this.notificationChannelId = notificationChannelId;\n    }\n\n    public void setNotificationChannelName(String notificationChannelName) {\n        this.notificationChannelName = notificationChannelName;\n    }\n\n    public void setNotification(Notification notification) {\n        this.notification = notification;\n    }\n\n    public void setNeedRecreateChannelId(boolean needRecreateChannelId) {\n        this.needRecreateChannelId = needRecreateChannelId;\n    }\n\n    private Notification buildDefaultNotification(Context context) {\n        String title = context.getString(R.string.default_filedownloader_notification_title);\n        String content =\n                context.getString(R.string.default_filedownloader_notification_content);\n        Notification.Builder builder = new Notification.Builder(context, notificationChannelId);\n        builder.setContentTitle(title).setContentText(content)\n                .setSmallIcon(DEFAULT_NOTIFICATION_ID);\n        return builder.build();\n    }\n\n    @Override\n    public String toString() {\n        return \"ForegroundServiceConfig{\"\n                + \"notificationId=\" + notificationId\n                + \", notificationChannelId='\" + notificationChannelId\n                + '\\''\n                + \", notificationChannelName='\" + notificationChannelName\n                + '\\''\n                + \", notification=\" + notification\n                + \", needRecreateChannelId=\" + needRecreateChannelId\n                + '}';\n    }\n\n    public static class Builder {\n        private int notificationId;\n        private String notificationChannelId;\n        private String notificationChannelName;\n        private Notification notification;\n        private boolean needRecreateChannelId;\n\n        public Builder notificationId(int notificationId) {\n            this.notificationId = notificationId;\n            return this;\n        }\n\n        public Builder notificationChannelId(String notificationChannelId) {\n            this.notificationChannelId = notificationChannelId;\n            return this;\n        }\n\n        public Builder notificationChannelName(String notificationChannelName) {\n            this.notificationChannelName = notificationChannelName;\n            return this;\n        }\n\n        public Builder notification(Notification notification) {\n            this.notification = notification;\n            return this;\n        }\n\n        public Builder needRecreateChannelId(boolean needRecreateChannelId) {\n            this.needRecreateChannelId = needRecreateChannelId;\n            return this;\n        }\n\n        public ForegroundServiceConfig build() {\n            final ForegroundServiceConfig config = new ForegroundServiceConfig();\n            config.setNotificationChannelId(notificationChannelId == null\n                    ? DEFAULT_NOTIFICATION_CHANNEL_ID : notificationChannelId);\n            config.setNotificationChannelName(notificationChannelName == null\n                    ? DEFAULT_NOTIFICATION_CHANNEL_NAME : notificationChannelName);\n            config.setNotificationId(notificationId == 0\n                    ? DEFAULT_NOTIFICATION_ID : notificationId);\n            config.setNeedRecreateChannelId(needRecreateChannelId);\n            config.setNotification(notification);\n            return config;\n        }\n    }\n}\n"
  },
  {
    "path": "library/src/main/java/com/liulishuo/filedownloader/services/IFileDownloadServiceHandler.java",
    "content": "/*\n * Copyright (c) 2015 LingoChamp Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *    http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage com.liulishuo.filedownloader.services;\n\nimport android.content.Intent;\nimport android.os.IBinder;\n\nimport com.liulishuo.filedownloader.services.FDServiceSharedHandler.FileDownloadServiceSharedConnection;\n\n/**\n * The handler for {@link FileDownloadService}.\n *\n * @see FileDownloadManager\n */\n@SuppressWarnings(\"UnusedParameters\")\ninterface IFileDownloadServiceHandler {\n    /**\n     * Will used to handling the onConnected in {@link FileDownloadServiceSharedConnection}.\n     * <p/>\n     * Called by the system every time a client explicitly starts the service by calling\n     * {@link android.content.Context#startService}.\n     */\n    void onStartCommand(Intent intent, int flags, int startId);\n\n    /**\n     * Will establish the connection with binder in {@link FDServiceSeparateHandler}\n     *\n     * @return Return the communication channel to the service. Nullable.\n     */\n    IBinder onBind(Intent intent);\n\n    /**\n     * Called by the system to notify a Service that it is no longer used and is being removed.\n     *\n     * @see FileDownloadServiceSharedConnection\n     * @see FDServiceSeparateHandler\n     */\n    void onDestroy();\n}\n"
  },
  {
    "path": "library/src/main/java/com/liulishuo/filedownloader/stream/FileDownloadOutputStream.java",
    "content": "/*\n * Copyright (c) 2015 LingoChamp Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *   http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.liulishuo.filedownloader.stream;\n\nimport java.io.FileDescriptor;\nimport java.io.IOException;\nimport java.io.OutputStream;\nimport java.io.SyncFailedException;\n\n/**\n * The output stream used to write the file for download.\n *\n * @see FileDownloadRandomAccessFile\n */\n\npublic interface FileDownloadOutputStream {\n\n    /**\n     * Writes <code>len</code> bytes from the specified byte array\n     * starting at offset <code>off</code> to this file.\n     *\n     * @param b   the data.\n     * @param off the start offset in the data.\n     * @param len the number of bytes to write.\n     * @throws IOException if an I/O error occurs.\n     */\n    void write(byte b[], int off, int len) throws IOException;\n\n    /**\n     * Flush all buffer to system and force all system buffers to synchronize with the underlying\n     * device.\n     * <p>\n     * This method must ensure all data whatever on buffers of VM or buffers of system for this\n     * output stream must persist on the physical media, otherwise the breakpoint will not be\n     * integrity.\n     *\n     * @throws SyncFailedException Thrown when the buffers cannot be flushed,\n     *                             or because the system cannot guarantee that all the\n     *                             buffers have been synchronized with physical media.\n     * @see OutputStream#flush()\n     * @see FileDescriptor#sync()\n     */\n    void flushAndSync() throws IOException;\n\n    /**\n     * Closes this output stream and releases any system resources associated with this stream. The\n     * general contract of <code>close</code> is that it closes the output stream. A closed stream\n     * cannot perform output operations and cannot be reopened.\n     * <p>\n     * The <code>close</code> method of <code>OutputStream</code> does nothing.\n     *\n     * @throws IOException if an I/O error occurs.\n     */\n    void close() throws IOException;\n\n    /**\n     * Sets the file-pointer offset, measured from the beginning of this file, at which the next\n     * read or write occurs.  The offset may be set beyond the end of the file. Setting the offset\n     * beyond the end of the file does not change the file length.  The file length will change only\n     * by writing after the offset has been set beyond the end of the file.\n     *\n     * @param offset the offset position, measured in bytes from the\n     *               beginning of the file, at which to set the file\n     *               pointer.\n     * @throws IOException            if <code>offset</code> is less than\n     *                                <code>0</code> or if an I/O error occurs.\n     * @throws IllegalAccessException if in this output stream doesn't support this function.\n     *                                You can return {@code false} in\n     *                                FileDownloadHelper.OutputStreamCreator#supportSeek()\n     *                                let the internal mechanism can predict this situation, and\n     *                                handle it smoothly.\n     * @see java.io.RandomAccessFile#seek(long)\n     * @see java.nio.channels.FileChannel#position(long)\n     */\n    void seek(long offset) throws IOException, IllegalAccessException;\n\n    /**\n     * Sets the length of this file.\n     * <p>\n     * <p> If the present length of the file as returned by the <code>length</code> method is\n     * greater than the <code>newLength</code> argument then the file will be truncated.  In this\n     * case, if the file offset as returned by the <code>getFilePointer</code> method is greater\n     * than <code>newLength</code> then after this method returns the offset will be equal to\n     * <code>newLength</code>.\n     * <p>\n     * <p> If the present length of the file as returned by the <code>length</code> method is\n     * smaller than the <code>newLength</code> argument then the file will be extended.  In this\n     * case, the contents of the extended portion of the file are not defined.\n     *\n     * @param newLength The desired length of the file\n     * @throws IOException            If an I/O error occurs\n     * @throws IllegalAccessException If in this output stream doesn't support this function.\n     * @see java.io.RandomAccessFile#setLength(long)\n     */\n    void setLength(final long newLength) throws IOException, IllegalAccessException;\n}\n"
  },
  {
    "path": "library/src/main/java/com/liulishuo/filedownloader/stream/FileDownloadRandomAccessFile.java",
    "content": "/*\n * Copyright (c) 2015 LingoChamp Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *   http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.liulishuo.filedownloader.stream;\n\nimport com.liulishuo.filedownloader.util.FileDownloadHelper;\n\nimport java.io.BufferedOutputStream;\nimport java.io.File;\nimport java.io.FileDescriptor;\nimport java.io.FileOutputStream;\nimport java.io.IOException;\nimport java.io.RandomAccessFile;\n\n/**\n * The FileDownloadOutputStream implemented using {@link RandomAccessFile}.\n */\n\npublic class FileDownloadRandomAccessFile implements FileDownloadOutputStream {\n    private final BufferedOutputStream out;\n    private final FileDescriptor fd;\n    private final RandomAccessFile randomAccess;\n\n    FileDownloadRandomAccessFile(File file) throws IOException {\n        randomAccess = new RandomAccessFile(file, \"rw\");\n        fd = randomAccess.getFD();\n        out = new BufferedOutputStream(new FileOutputStream(randomAccess.getFD()));\n    }\n\n    @Override\n    public void write(byte[] b, int off, int len) throws IOException {\n        out.write(b, off, len);\n    }\n\n    @Override\n    public void flushAndSync() throws IOException {\n        out.flush();\n        fd.sync();\n    }\n\n    @Override\n    public void close() throws IOException {\n        out.close();\n        randomAccess.close();\n    }\n\n    @Override\n    public void seek(long offset) throws IOException {\n        randomAccess.seek(offset);\n    }\n\n    @Override\n    public void setLength(long totalBytes) throws IOException {\n        randomAccess.setLength(totalBytes);\n    }\n\n    public static class Creator implements FileDownloadHelper.OutputStreamCreator {\n\n        @Override\n        public FileDownloadOutputStream create(File file) throws IOException {\n            return new FileDownloadRandomAccessFile(file);\n        }\n\n        @Override\n        public boolean supportSeek() {\n            return true;\n        }\n    }\n}\n"
  },
  {
    "path": "library/src/main/java/com/liulishuo/filedownloader/util/DownloadServiceNotConnectedHelper.java",
    "content": "/*\n * Copyright (c) 2015 LingoChamp Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *    http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage com.liulishuo.filedownloader.util;\n\nimport android.app.Notification;\n\nimport com.liulishuo.filedownloader.FileDownloader;\nimport com.liulishuo.filedownloader.model.FileDownloadStatus;\n\n/**\n * The helper for handling the case of requesting do something in the downloader service but the\n * downloader service isn't connected yet.\n *\n * @see FileDownloader#insureServiceBind()\n * @see FileDownloader#insureServiceBindAsync()\n */\npublic class DownloadServiceNotConnectedHelper {\n\n    private static final String CAUSE = \", but the download service isn't connected yet.\";\n    private static final String TIPS = \"\\nYou can use FileDownloader#isServiceConnected() to check\"\n            + \" whether the service has been connected, \\nbesides you can use following functions\"\n            + \" easier to control your code invoke after the service has been connected: \\n\"\n            + \"1. FileDownloader#bindService(Runnable)\\n\"\n            + \"2. FileDownloader#insureServiceBind()\\n\"\n            + \"3. FileDownloader#insureServiceBindAsync()\";\n\n    public static boolean start(final String url, final String path,\n                                final boolean pathAsDirectory) {\n        log(\"request start the task([%s], [%s], [%B]) in the download service\", url, path,\n                pathAsDirectory);\n        return false;\n    }\n\n    public static boolean pause(final int id) {\n        log(\"request pause the task[%d] in the download service\", id);\n        return false;\n    }\n\n    public static boolean isDownloading(final String url, final String path) {\n        log(\"request check the task([%s], [%s]) is downloading in the download service\", url, path);\n        return false;\n    }\n\n    public static long getSofar(final int id) {\n        log(\"request get the downloaded so far byte for the task[%d] in the download service\", id);\n        return 0;\n    }\n\n    public static long getTotal(final int id) {\n        log(\"request get the total byte for the task[%d] in the download service\", id);\n        return 0;\n    }\n\n    public static byte getStatus(final int id) {\n        log(\"request get the status for the task[%d] in the download service\", id);\n        return FileDownloadStatus.INVALID_STATUS;\n    }\n\n    public static void pauseAllTasks() {\n        log(\"request pause all tasks in the download service\");\n    }\n\n    public static boolean isIdle() {\n        log(\"request check the download service is idle\");\n        return true;\n    }\n\n    public static void startForeground(int notificationId, Notification notification) {\n        log(\"request set the download service as the foreground service([%d],[%s]),\",\n                notificationId, notification);\n    }\n\n    public static void stopForeground(boolean removeNotification) {\n        log(\"request cancel the foreground status[%B] for the download service\",\n                removeNotification);\n    }\n\n    public static boolean setMaxNetworkThreadCount(int count) {\n        log(\"request set the max network thread count[%d] in the download service\", count);\n        return false;\n    }\n\n    public static boolean clearTaskData(int id) {\n        log(\"request clear the task[%d] data in the database\", id);\n        return false;\n    }\n\n    public static boolean clearAllTaskData() {\n        log(\"request clear all tasks data in the database\");\n        return false;\n    }\n\n    private static void log(String message, Object... args) {\n        FileDownloadLog.w(DownloadServiceNotConnectedHelper.class, message + CAUSE + TIPS, args);\n    }\n\n}\n"
  },
  {
    "path": "library/src/main/java/com/liulishuo/filedownloader/util/ExtraKeys.java",
    "content": "/*\n * Copyright (c) 2015 LingoChamp Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *   http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.liulishuo.filedownloader.util;\n\npublic class ExtraKeys {\n\n    public static final String IS_FOREGROUND = \"is_foreground\";\n}\n"
  },
  {
    "path": "library/src/main/java/com/liulishuo/filedownloader/util/FileDownloadExecutors.java",
    "content": "/*\n * Copyright (c) 2015 LingoChamp Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *    http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.liulishuo.filedownloader.util;\n\nimport java.util.concurrent.LinkedBlockingQueue;\nimport java.util.concurrent.SynchronousQueue;\nimport java.util.concurrent.ThreadFactory;\nimport java.util.concurrent.ThreadPoolExecutor;\nimport java.util.concurrent.TimeUnit;\nimport java.util.concurrent.atomic.AtomicInteger;\n\n/**\n * Executors are used in entire FileDownloader internal for managing different threads.\n * <p>\n * All thread pools in FileDownloader will comply with:\n * <p>\n * The default thread count is 0, and the maximum pool size is {@code nThreads}; When there are less\n * than {@code nThreads} threads running, a new thread is created to handle the request, but when it\n * turn to idle and the interval time of waiting for new task more than {@code DEFAULT_IDLE_SECOND}\n * second, the thread will be terminate to reduce the cost of resources.\n */\npublic class FileDownloadExecutors {\n    private static final int DEFAULT_IDLE_SECOND = 15;\n\n    public static ThreadPoolExecutor newFixedThreadPool(String prefix) {\n        return new ThreadPoolExecutor(0, Integer.MAX_VALUE,\n                DEFAULT_IDLE_SECOND, TimeUnit.SECONDS,\n                new SynchronousQueue<Runnable>(), new FileDownloadThreadFactory(prefix));\n    }\n\n    public static ThreadPoolExecutor newDefaultThreadPool(int nThreads, String prefix) {\n        return newDefaultThreadPool(nThreads, new LinkedBlockingQueue<Runnable>(), prefix);\n    }\n\n    public static ThreadPoolExecutor newDefaultThreadPool(int nThreads,\n                                                          LinkedBlockingQueue<Runnable> queue,\n                                                          String prefix) {\n        final ThreadPoolExecutor executor = new ThreadPoolExecutor(nThreads, nThreads,\n                DEFAULT_IDLE_SECOND, TimeUnit.SECONDS, queue,\n                new FileDownloadThreadFactory(prefix));\n        executor.allowCoreThreadTimeOut(true);\n        return executor;\n    }\n\n    static class FileDownloadThreadFactory implements ThreadFactory {\n        private static final AtomicInteger POOL_NUMBER = new AtomicInteger(1);\n        private final String namePrefix;\n        private final ThreadGroup group;\n        private final AtomicInteger threadNumber = new AtomicInteger(1);\n\n        FileDownloadThreadFactory(String prefix) {\n            group = Thread.currentThread().getThreadGroup();\n            namePrefix = FileDownloadUtils.getThreadPoolName(prefix);\n        }\n\n        @Override\n        public Thread newThread(Runnable r) {\n            Thread t = new Thread(group, r, namePrefix + threadNumber.getAndIncrement(), 0);\n\n            if (t.isDaemon()) t.setDaemon(false);\n            if (t.getPriority() != Thread.NORM_PRIORITY) t.setPriority(Thread.NORM_PRIORITY);\n            return t;\n        }\n    }\n\n}\n"
  },
  {
    "path": "library/src/main/java/com/liulishuo/filedownloader/util/FileDownloadHelper.java",
    "content": "/*\n * Copyright (c) 2015 LingoChamp Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *    http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.liulishuo.filedownloader.util;\n\nimport android.annotation.SuppressLint;\nimport android.content.Context;\n\nimport com.liulishuo.filedownloader.IThreadPoolMonitor;\nimport com.liulishuo.filedownloader.connection.FileDownloadConnection;\nimport com.liulishuo.filedownloader.database.SqliteDatabaseImpl;\nimport com.liulishuo.filedownloader.exception.PathConflictException;\nimport com.liulishuo.filedownloader.message.MessageSnapshotFlow;\nimport com.liulishuo.filedownloader.message.MessageSnapshotTaker;\nimport com.liulishuo.filedownloader.model.FileDownloadModel;\nimport com.liulishuo.filedownloader.database.FileDownloadDatabase;\nimport com.liulishuo.filedownloader.stream.FileDownloadOutputStream;\nimport com.liulishuo.filedownloader.stream.FileDownloadRandomAccessFile;\n\nimport java.io.File;\nimport java.io.FileNotFoundException;\nimport java.io.IOException;\n\n/**\n * The helper for cache the {@code APP_CONTEXT} and {@code OK_HTTP_CLIENT} for the main process and\n * the filedownloader process.\n */\npublic class FileDownloadHelper {\n\n    @SuppressLint(\"StaticFieldLeak\")\n    private static Context APP_CONTEXT;\n\n    public static void holdContext(final Context context) {\n        APP_CONTEXT = context;\n    }\n\n    public static Context getAppContext() {\n        return APP_CONTEXT;\n    }\n\n    @SuppressWarnings(\"UnusedParameters\")\n    public interface IdGenerator {\n        /**\n         * Invoke this method when the data is restoring from the database.\n         *\n         * @param oldId           the old download id which is generated through the different.\n         * @param url             the url path.\n         * @param path            the download path.\n         * @param pathAsDirectory {@code true}: if the {@code path} is absolute directory to store\n         *                        the downloading file, and the {@code filename} will be found in\n         *                        contentDisposition from the response as default, if can't find\n         *                        contentDisposition,the {@code filename} will be generated by\n         *                        {@link FileDownloadUtils#generateFileName(String)}  with\n         *                        {@code url}.\n         *                        </p>\n         *                        {@code false}: if the {@code path} = (absolute directory/filename)\n         * @return the new download id.\n         */\n        int transOldId(final int oldId, final String url, final String path,\n                       final boolean pathAsDirectory);\n\n        /**\n         * Invoke this method when there is a new task from very beginning.\n         * <p>\n         * Important Ting: this method would be used on the FileDownloadService and the upper-layer,\n         * so as default FileDownloadService is running on the `:filedownloader` process, and\n         * upper-layer\n         * is on the user process, in this case, if you want to cache something on this instance, it\n         * would be two different caches on two processes.\n         * <p>\n         * Tips: if you want the FileDownloadService runs on the upper-layer process too, just\n         * config {@code process.non-separate=true} on the filedownloader.properties, more detail\n         * please move to {@link FileDownloadProperties}\n         *\n         * @param url             the download url.\n         * @param path            the download path.\n         * @param pathAsDirectory {@code true}: if the {@code path} is absolute directory to store\n         *                        the downloading file, and the {@code filename} will be found in\n         *                        contentDisposition from the response as default, if can't find\n         *                        contentDisposition,the {@code filename} will be generated by\n         *                        {@link FileDownloadUtils#generateFileName(String)}  with\n         *                        {@code url}.\n         *                        </p>\n         *                        {@code false}: if the {@code path} = (absolute directory/filename)\n         * @return the download task identify.\n         */\n        int generateId(final String url, final String path, final boolean pathAsDirectory);\n    }\n\n    @SuppressWarnings(\"UnusedParameters\")\n    public interface ConnectionCountAdapter {\n        /**\n         * Before invoke this method to determine how many connection will be used to downloading\n         * this task,\n         * there are several conditions must be confirmed:\n         * <p>\n         * 1. the connection is support multiple connection(SUPPORT\"Partial Content(206)\" AND NOT\n         * Chunked)\n         * 2. the current {@link FileDownloadOutputStream} support seek(The default\n         * one({@link FileDownloadRandomAccessFile} is support)\n         * 3. this is a new task NOT resume from breakpoint( If the task resume from breakpoint\n         * the connection count would be using\n         * the one you determined when the task\n         * first created ).\n         * <p/>\n         * The best strategy is refer to how much speed of each connection for the ip:port not file\n         * size.\n         *\n         * @param downloadId  the download id.\n         * @param url         the task url.\n         * @param path        the task path.\n         * @param totalLength the total length of the file.\n         * @return the count of connection you want for the task. the value must be large than 0.\n         */\n        int determineConnectionCount(int downloadId, String url, String path, long totalLength);\n    }\n\n    public interface DatabaseCustomMaker {\n        /**\n         * The database is used for storing the {@link FileDownloadModel}.\n         * <p/>\n         * The data stored in the database is only used for task resumes from the breakpoint.\n         * <p>\n         * The task of the data stored in the database must be a task that has not finished\n         * downloading yet, and if the task has finished downloading, its data will be\n         * {@link FileDownloadDatabase#remove(int)} from the database, since that data is no longer\n         * available for resumption of its task pass.\n         *\n         * @return Nullable, Customize {@link FileDownloadDatabase} which will be used for storing\n         * downloading model.\n         * @see SqliteDatabaseImpl\n         */\n        FileDownloadDatabase customMake();\n    }\n\n    public interface OutputStreamCreator {\n        /**\n         * The output stream creator is used for creating {@link FileDownloadOutputStream} which is\n         * used to write the input stream to the file for downloading.\n         * <p>\n         * <strong>Note:</strong> please create a output stream which append the content to the\n         * exist file, which means that bytes would be written to the end of the file rather than\n         * the beginning.\n         *\n         * @param file the file will used for storing the downloading content.\n         * @return The output stream used to write downloading byte array to the {@code file}.\n         * @throws FileNotFoundException if the file exists but is a directory\n         *                               rather than a regular file, does not exist but cannot\n         *                               be created, or cannot be opened for any other reason\n         * @see FileDownloadRandomAccessFile.Creator\n         */\n        FileDownloadOutputStream create(File file) throws IOException;\n\n        /**\n         * @return {@code true} if the {@link FileDownloadOutputStream} is created through\n         * {@link #create(File)} support {@link FileDownloadOutputStream#seek(long)} function.\n         * If the {@link FileDownloadOutputStream} is created through {@link #create(File)} doesn't\n         * support {@link FileDownloadOutputStream#seek(long)}, please return {@code false}, in\n         * order to let the internal mechanism can predict this situation, and handle it smoothly.\n         */\n        boolean supportSeek();\n    }\n\n    public interface ConnectionCreator {\n        /**\n         * The connection creator is used for creating {@link FileDownloadConnection} component\n         * which is used to use some protocol to connect to the remote server.\n         *\n         * @param url the uniform resource locator, which direct the aim resource we need to connect\n         * @return The connection creator.\n         * @throws IOException if an I/O exception occurs.\n         */\n        FileDownloadConnection create(String url) throws IOException;\n    }\n\n    /**\n     * @param id              the {@code id} used for filter out which task would be notified the\n     *                        'completed' message if need.\n     * @param path            if the file with {@code path} is exist it means the relate task would\n     *                        be completed.\n     * @param forceReDownload whether the task is force to re-download ignore whether the file has\n     *                        been exist or not.\n     * @param flowDirectly    {@code true} if flow the message if need directly without throw to the\n     *                        message-queue.\n     * @return whether the task with {@code id} has been downloaded.\n     */\n    public static boolean inspectAndInflowDownloaded(int id, String path, boolean forceReDownload,\n                                                     boolean flowDirectly) {\n        if (forceReDownload) {\n            return false;\n        }\n\n        if (path != null) {\n            final File file = new File(path);\n            if (file.exists()) {\n                MessageSnapshotFlow.getImpl().inflow(MessageSnapshotTaker.\n                        catchCanReusedOldFile(id, file, flowDirectly));\n                return true;\n            }\n        }\n\n        return false;\n    }\n\n    /**\n     * @param id           the {@code id} used for filter out which task would be notified the\n     *                     'warn' message if need.\n     * @param model        the target model will be checked for judging whether it is downloading.\n     * @param monitor      the monitor for download-thread.\n     * @param flowDirectly {@code true} if flow the message if need directly without throw to the\n     *                     message-queue.\n     * @return whether the {@code model} is downloading.\n     */\n    public static boolean inspectAndInflowDownloading(int id, FileDownloadModel model,\n                                                      IThreadPoolMonitor monitor,\n                                                      boolean flowDirectly) {\n        if (monitor.isDownloading(model)) {\n            MessageSnapshotFlow.getImpl().\n                    inflow(MessageSnapshotTaker.catchWarn(id, model.getSoFar(), model.getTotal(),\n                            flowDirectly));\n            return true;\n        }\n\n        return false;\n    }\n\n    /**\n     * @param id             the {@code id} used for filter out which task would be notified the\n     *                       'error' message if need.\n     * @param sofar          the so far bytes of the current checking-task.\n     * @param tempFilePath   the temp file path(file path used for downloading) for the current\n     *                       checking-task.\n     * @param targetFilePath the target file path for the current checking-task.\n     * @param monitor        the monitor for download-thread.\n     * @return whether the task with {@code id} is refused to start, because of there is an another\n     * running task with the same {@code tempFilePath}.\n     */\n    public static boolean inspectAndInflowConflictPath(int id, long sofar,\n                                                       String tempFilePath, String targetFilePath,\n                                                       IThreadPoolMonitor monitor) {\n        if (targetFilePath != null && tempFilePath != null) {\n            final int anotherSameTempPathTaskId =\n                    monitor.findRunningTaskIdBySameTempPath(tempFilePath, id);\n            if (anotherSameTempPathTaskId != 0) {\n                MessageSnapshotFlow.getImpl().\n                        inflow(MessageSnapshotTaker.catchException(id, sofar,\n                                new PathConflictException(anotherSameTempPathTaskId, tempFilePath,\n                                        targetFilePath)));\n                return true;\n            }\n        }\n\n        return false;\n    }\n}\n\n"
  },
  {
    "path": "library/src/main/java/com/liulishuo/filedownloader/util/FileDownloadLog.java",
    "content": "/*\n * Copyright (c) 2015 LingoChamp Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *    http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.liulishuo.filedownloader.util;\n\nimport android.util.Log;\n\n/**\n * The log printer help to control all logs in FileDownloader.\n * <p>\n * If you want to print all priority FileDownloader logs, please set true to {@link #NEED_LOG},\n * otherwise, we just print the log which priority higher than or equal to {@link Log#WARN}.\n */\npublic class FileDownloadLog {\n\n    public static boolean NEED_LOG = false;\n\n    private static final String TAG = \"FileDownloader.\";\n\n    public static void e(Object o, Throwable e, String msg, Object... args) {\n        log(Log.ERROR, o, e, msg, args);\n    }\n\n    public static void e(Object o, String msg, Object... args) {\n        log(Log.ERROR, o, msg, args);\n    }\n\n    public static void i(Object o, String msg, Object... args) {\n        log(Log.INFO, o, msg, args);\n    }\n\n    public static void d(Object o, String msg, Object... args) {\n        log(Log.DEBUG, o, msg, args);\n    }\n\n    public static void w(Object o, String msg, Object... args) {\n        log(Log.WARN, o, msg, args);\n    }\n\n    public static void v(Object o, String msg, Object... args) {\n        log(Log.VERBOSE, o, msg, args);\n    }\n\n    private static void log(int priority, Object o, String message, Object... args) {\n        log(priority, o, null, message, args);\n    }\n\n    private static void log(int priority, Object o, Throwable throwable, String message,\n                            Object... args) {\n        final boolean force = priority >= Log.WARN;\n        if (!force && !NEED_LOG) {\n            return;\n        }\n\n        Log.println(priority, getTag(o), FileDownloadUtils.formatString(message, args));\n        if (throwable != null) {\n            throwable.printStackTrace();\n        }\n    }\n\n    private static String getTag(final Object o) {\n        return TAG + ((o instanceof Class)\n                ? ((Class) o).getSimpleName() : o.getClass().getSimpleName());\n    }\n}\n"
  },
  {
    "path": "library/src/main/java/com/liulishuo/filedownloader/util/FileDownloadProperties.java",
    "content": "/*\n * Copyright (c) 2015 LingoChamp Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *    http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage com.liulishuo.filedownloader.util;\n\nimport com.liulishuo.filedownloader.services.FileDownloadBroadcastHandler;\n\nimport java.io.FileNotFoundException;\nimport java.io.IOException;\nimport java.io.InputStream;\nimport java.util.Properties;\n\n/**\n * You can customize the FileDownloader Engine by add filedownloader.properties file in your assets\n * folder. Example: /demo/src/main/assets/filedownloader.properties\n * <p/>\n * Rules: key=value\n * <p/>\n * Supported keys:\n * <p/>\n * Key {@code http.lenient}\n * Value: {@code true} or {@code false}\n * Default: {@code false}.\n * Such as: http.lenient=false\n * Description:\n * If you occur exception: 'can't know the size of the download file, and its Transfer-Encoding is\n * not Chunked either', but you want to ignore such exception, set true, will deal with it as the\n * case of transfer encoding chunk.\n * If true, will ignore HTTP response header does not has content-length either not chunk transfer\n * encoding.\n * <p/>\n * Key {@code process.non-separate}\n * Value: {@code true} or {@code false}\n * Default: {@code false}.\n * Such as: process.non-separate=false\n * Description:\n * The FileDownloadService runs in the separate process ':filedownloader' as default, if you want\n * to run the FileDownloadService in the main process, just set true.\n * <p/>\n * Key {@code download.min-progress-step}\n * Value: [0, {@link Integer#MAX_VALUE}]\n * Default: 65536, which follow the value in com.android.providers.downloads.Constants.\n * Such as: download.min-progress-step=65536\n * Description:\n * The min buffered so far bytes.\n * Used for adjudging whether is time to sync the downloaded so far bytes to database and make sure\n * sync the downloaded buffer to local file.\n * More smaller more frequently, then download more slowly, but will more safer in scene of the\n * process is killed unexpectedly.\n * <p/>\n * Key {@code download.min-progress-time}\n * Value: [0, {@link Long#MAX_VALUE}]\n * Default: 2000, which follow the value in com.android.providers.downloads.Constants.\n * Such as: download.min-progress-time=2000\n * Description:\n * The min buffered millisecond.\n * Used for adjudging whether is time to sync the downloaded so far bytes to database and make sure\n * sync the downloaded buffer to local file.\n * More smaller more frequently, then download more slowly, but will more safer in scene of the\n * process is killed unexpectedly.\n * <p/>\n * Key {@code download.max-network-thread-count}\n * Value: [1, 12]\n * Default: 3.\n * Such as: download.max-network-thread-count=3\n * Description:\n * The maximum network thread count for downloading simultaneously.\n * FileDownloader is designed to download 3 files simultaneously as maximum size as default, and the\n * rest of the task is in the FIFO(First In First Out) pending queue.\n * Because the network resource is limited to one device, it means if FileDownloader start\n * downloading tasks unlimited simultaneously, it will be blocked by lack of the network resource,\n * and more useless CPU occupy.\n * The relative efficiency of 3 is higher than others(As Fresco or Picasso do), But for case by case\n * FileDownloader is support to configure for this.\n * Max 12, min 1. If the value more than {@code max} will be replaced with {@code max}; If the value\n * less than {@code min} will be replaced with {@code min}.\n * <p/>\n * Key {@code file.non-pre-allocation}\n * Value: {@code true} or {@code false}\n * Default: {@code false}.\n * Such as: file.non-pre-allocation=false\n * Description:\n * FileDownloader is designed to create the file and pre-allocates the 'content-length' space for it\n * when start downloading.Because FileDownloader want to prevent the space is not enough to store\n * coming data in downloading state as default.\n * <p/>\n * Key {@code broadcast.completed}\n * Value: {@code true} or {@code false}\n * Default: {@code false}.\n * Such as: broadcast.completed=false\n * Description:\n * Whether need to post an broadcast when downloading is completed.\n * This option is very useful when you download something silent on the background on the\n * filedownloader process, and the main process is killed, but you want to do something on the main\n * process when tasks are completed downloading on the filedownloader process, so you can set this\n * one to `true`, then when a task is completed task, you will receive the broadcast, and the main\n * process will be relaunched to handle the broadcast.\n * <p>\n * If you want to receive such broadcast, you also need to register receiver with\n * 'filedownloader.intent.action.completed' action name on 'AndroidManifest.xml'.\n * <p>\n * You can use {@link FileDownloadBroadcastHandler} class to parse the received intent.\n * <p/>\n * Key {code download.trial-connection-head-method}\n * Value: {@code true} or {@code false}\n * Default: {@code false}.\n * Such as download.trial-connection-head-method=false\n * Description:\n * Whether you want the first trial connection with HEAD method to request to backend or not, if\n * this value is true, the first trial connection will with HEAD method instead of GET method and\n * then you will reduce 1 byte cost on the response body, but if the backend can't support HEAD\n * method you will receive 405 response code and failed to download.\n */\npublic class FileDownloadProperties {\n\n    private static final String KEY_HTTP_LENIENT = \"http.lenient\";\n    private static final String KEY_PROCESS_NON_SEPARATE = \"process.non-separate\";\n    private static final String KEY_DOWNLOAD_MIN_PROGRESS_STEP = \"download.min-progress-step\";\n    private static final String KEY_DOWNLOAD_MIN_PROGRESS_TIME = \"download.min-progress-time\";\n    private static final String KEY_DOWNLOAD_MAX_NETWORK_THREAD_COUNT =\n            \"download.max-network-thread-count\";\n    private static final String KEY_FILE_NON_PRE_ALLOCATION = \"file.non-pre-allocation\";\n    private static final String KEY_BROADCAST_COMPLETED = \"broadcast.completed\";\n    private static final String KEY_TRIAL_CONNECTION_HEAD_METHOD\n            = \"download.trial-connection-head-method\";\n\n    public final int downloadMinProgressStep;\n    public final long downloadMinProgressTime;\n    public final boolean httpLenient;\n    public final boolean processNonSeparate;\n    public final int downloadMaxNetworkThreadCount;\n    public final boolean fileNonPreAllocation;\n    public final boolean broadcastCompleted;\n    public final boolean trialConnectionHeadMethod;\n\n    public static class HolderClass {\n        private static final FileDownloadProperties INSTANCE = new FileDownloadProperties();\n    }\n\n    public static FileDownloadProperties getImpl() {\n        return HolderClass.INSTANCE;\n    }\n\n    private static final String TRUE_STRING = \"true\";\n    private static final String FALSE_STRING = \"false\";\n\n    // init properties, normally consume <= 2ms\n    private FileDownloadProperties() {\n        if (FileDownloadHelper.getAppContext() == null) {\n            throw new IllegalStateException(\"Please invoke the 'FileDownloader#setup' before using \"\n                    + \"FileDownloader. If you want to register some components on FileDownloader \"\n                    + \"please invoke the 'FileDownloader#setupOnApplicationOnCreate' on the \"\n                    + \"'Application#onCreate' first.\");\n        }\n\n        final long start = System.currentTimeMillis();\n        String httpLenient = null;\n        String processNonSeparate = null;\n        String downloadMinProgressStep = null;\n        String downloadMinProgressTime = null;\n        String downloadMaxNetworkThreadCount = null;\n        String fileNonPreAllocation = null;\n        String broadcastCompleted = null;\n        String downloadTrialConnectionHeadMethod = null;\n\n        Properties p = new Properties();\n        InputStream inputStream = null;\n\n        try {\n            inputStream = FileDownloadHelper.getAppContext().getAssets().\n                    open(\"filedownloader.properties\");\n            if (inputStream != null) {\n                p.load(inputStream);\n                httpLenient = p.getProperty(KEY_HTTP_LENIENT);\n                processNonSeparate = p.getProperty(KEY_PROCESS_NON_SEPARATE);\n                downloadMinProgressStep = p.getProperty(KEY_DOWNLOAD_MIN_PROGRESS_STEP);\n                downloadMinProgressTime = p.getProperty(KEY_DOWNLOAD_MIN_PROGRESS_TIME);\n                downloadMaxNetworkThreadCount = p\n                        .getProperty(KEY_DOWNLOAD_MAX_NETWORK_THREAD_COUNT);\n                fileNonPreAllocation = p.getProperty(KEY_FILE_NON_PRE_ALLOCATION);\n                broadcastCompleted = p.getProperty(KEY_BROADCAST_COMPLETED);\n                downloadTrialConnectionHeadMethod = p.getProperty(KEY_TRIAL_CONNECTION_HEAD_METHOD);\n            }\n        } catch (IOException e) {\n            if (e instanceof FileNotFoundException) {\n                if (FileDownloadLog.NEED_LOG) {\n                    FileDownloadLog.d(FileDownloadProperties.class,\n                            \"not found filedownloader.properties\");\n                }\n            } else {\n                e.printStackTrace();\n            }\n        } finally {\n            if (inputStream != null) {\n                try {\n                    inputStream.close();\n                } catch (IOException e) {\n                    e.printStackTrace();\n                }\n            }\n        }\n\n\n        //http.lenient\n        if (httpLenient != null) {\n            if (!httpLenient.equals(TRUE_STRING) && !httpLenient.equals(FALSE_STRING)) {\n                throw new IllegalStateException(\n                        FileDownloadUtils.formatString(\"the value of '%s' must be '%s' or '%s'\",\n                                KEY_HTTP_LENIENT, TRUE_STRING, FALSE_STRING));\n            }\n            this.httpLenient = httpLenient.equals(TRUE_STRING);\n        } else {\n            this.httpLenient = false;\n        }\n\n        //process.non-separate\n        if (processNonSeparate != null) {\n            if (!processNonSeparate.equals(TRUE_STRING)\n                    && !processNonSeparate.equals(FALSE_STRING)) {\n                throw new IllegalStateException(\n                        FileDownloadUtils.formatString(\"the value of '%s' must be '%s' or '%s'\",\n                                KEY_PROCESS_NON_SEPARATE, TRUE_STRING, FALSE_STRING));\n            }\n            this.processNonSeparate = processNonSeparate.equals(TRUE_STRING);\n        } else {\n            this.processNonSeparate = false;\n        }\n\n        //download.min-progress-step\n        if (downloadMinProgressStep != null) {\n            int processDownloadMinProgressStep = Integer.valueOf(downloadMinProgressStep);\n            processDownloadMinProgressStep = Math.max(0, processDownloadMinProgressStep);\n            this.downloadMinProgressStep = processDownloadMinProgressStep;\n        } else {\n            this.downloadMinProgressStep = 65536;\n        }\n\n        //download.min-progress-time\n        if (downloadMinProgressTime != null) {\n            long processDownloadMinProgressTime = Long.valueOf(downloadMinProgressTime);\n            processDownloadMinProgressTime = Math.max(0, processDownloadMinProgressTime);\n            this.downloadMinProgressTime = processDownloadMinProgressTime;\n        } else {\n            this.downloadMinProgressTime = 2000L;\n        }\n\n        //download.max-network-thread-count\n        if (downloadMaxNetworkThreadCount != null) {\n            this.downloadMaxNetworkThreadCount = getValidNetworkThreadCount(\n                    Integer.valueOf(downloadMaxNetworkThreadCount));\n        } else {\n            this.downloadMaxNetworkThreadCount = 3;\n        }\n\n        // file.non-pre-allocation\n        if (fileNonPreAllocation != null) {\n            if (!fileNonPreAllocation.equals(TRUE_STRING)\n                    && !fileNonPreAllocation.equals(FALSE_STRING)) {\n                throw new IllegalStateException(\n                        FileDownloadUtils.formatString(\"the value of '%s' must be '%s' or '%s'\",\n                                KEY_FILE_NON_PRE_ALLOCATION, TRUE_STRING, FALSE_STRING));\n            }\n            this.fileNonPreAllocation = fileNonPreAllocation.equals(TRUE_STRING);\n        } else {\n            this.fileNonPreAllocation = false;\n        }\n\n        // broadcast.completed\n        if (broadcastCompleted != null) {\n            if (!broadcastCompleted.equals(TRUE_STRING)\n                    && !broadcastCompleted.equals(FALSE_STRING)) {\n                throw new IllegalStateException(\n                        FileDownloadUtils.formatString(\"the value of '%s' must be '%s' or '%s'\",\n                                KEY_BROADCAST_COMPLETED, TRUE_STRING, FALSE_STRING));\n            }\n            this.broadcastCompleted = broadcastCompleted.equals(TRUE_STRING);\n\n        } else {\n            this.broadcastCompleted = false;\n        }\n\n        // download.trial-connection-head-method\n        if (downloadTrialConnectionHeadMethod != null) {\n            if (!downloadTrialConnectionHeadMethod.equals(TRUE_STRING)\n                    && !downloadTrialConnectionHeadMethod.equals(FALSE_STRING)) {\n                throw new IllegalStateException(\n                        FileDownloadUtils.formatString(\"the value of '%s' must be '%s' or '%s'\",\n                                KEY_TRIAL_CONNECTION_HEAD_METHOD, TRUE_STRING, FALSE_STRING));\n            }\n            this.trialConnectionHeadMethod = downloadTrialConnectionHeadMethod.equals(TRUE_STRING);\n        } else {\n            this.trialConnectionHeadMethod = false;\n        }\n\n        if (FileDownloadLog.NEED_LOG) {\n            FileDownloadLog.i(FileDownloadProperties.class, \"init properties %d\\n load properties:\"\n                            + \" %s=%B; %s=%B; %s=%d; %s=%d; %s=%d; %s=%B; %s=%B; %s=%B\",\n                    System.currentTimeMillis() - start,\n                    KEY_HTTP_LENIENT, this.httpLenient,\n                    KEY_PROCESS_NON_SEPARATE, this.processNonSeparate,\n                    KEY_DOWNLOAD_MIN_PROGRESS_STEP, this.downloadMinProgressStep,\n                    KEY_DOWNLOAD_MIN_PROGRESS_TIME, this.downloadMinProgressTime,\n                    KEY_DOWNLOAD_MAX_NETWORK_THREAD_COUNT, this.downloadMaxNetworkThreadCount,\n                    KEY_FILE_NON_PRE_ALLOCATION, this.fileNonPreAllocation,\n                    KEY_BROADCAST_COMPLETED, this.broadcastCompleted,\n                    KEY_TRIAL_CONNECTION_HEAD_METHOD, this.trialConnectionHeadMethod);\n        }\n    }\n\n    public static int getValidNetworkThreadCount(int requireCount) {\n        int maxValidNetworkThreadCount = 12;\n        int minValidNetworkThreadCount = 1;\n\n        if (requireCount > maxValidNetworkThreadCount) {\n            FileDownloadLog.w(FileDownloadProperties.class, \"require the count of network thread  \"\n                            + \"is %d, what is more than the max valid count(%d), so adjust to %d \"\n                            + \"auto\",\n                    requireCount, maxValidNetworkThreadCount, maxValidNetworkThreadCount);\n            return maxValidNetworkThreadCount;\n        } else if (requireCount < minValidNetworkThreadCount) {\n            FileDownloadLog.w(FileDownloadProperties.class, \"require the count of network thread  \"\n                            + \"is %d, what is less than the min valid count(%d), so adjust to %d\"\n                            + \" auto\",\n                    requireCount, minValidNetworkThreadCount, minValidNetworkThreadCount);\n            return minValidNetworkThreadCount;\n        }\n\n        return requireCount;\n    }\n}\n"
  },
  {
    "path": "library/src/main/java/com/liulishuo/filedownloader/util/FileDownloadSerialQueue.java",
    "content": "/*\n * Copyright (c) 2015 LingoChamp Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *   http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.liulishuo.filedownloader.util;\n\nimport android.os.Handler;\nimport android.os.HandlerThread;\nimport android.os.Message;\n\nimport com.liulishuo.filedownloader.BaseDownloadTask;\n\nimport java.lang.ref.WeakReference;\nimport java.util.ArrayList;\nimport java.util.List;\nimport java.util.concurrent.BlockingQueue;\nimport java.util.concurrent.LinkedBlockingQueue;\n\n/**\n * The serial queue, what used to dynamically increase tasks, and tasks in the queue will\n * automatically start download one by one.\n */\n\npublic class FileDownloadSerialQueue {\n\n    private final Object operationLock = new Object();\n    private final BlockingQueue<BaseDownloadTask> mTasks = new LinkedBlockingQueue<>();\n    private final List<BaseDownloadTask> pausedList = new ArrayList<>();\n    private final HandlerThread mHandlerThread;\n    private final Handler mHandler;\n\n    private static final int WHAT_NEXT = 1;\n    public static final int ID_INVALID = 0;\n\n    volatile BaseDownloadTask workingTask;\n    final SerialFinishCallback finishCallback;\n    volatile boolean paused = false;\n\n    public FileDownloadSerialQueue() {\n        mHandlerThread = new HandlerThread(\n                FileDownloadUtils.getThreadPoolName(\"SerialDownloadManager\"));\n        mHandlerThread.start();\n        mHandler = new Handler(mHandlerThread.getLooper(), new SerialLoop());\n        finishCallback = new SerialFinishCallback(new WeakReference<>(this));\n        sendNext();\n    }\n\n    /**\n     * Enqueues the given task sometime in the serial queue. If the {@code task} is in the head of\n     * the serial queue, the {@code task} will be started automatically.\n     */\n    public void enqueue(BaseDownloadTask task) {\n        synchronized (finishCallback) {\n            if (paused) {\n                pausedList.add(task);\n                return;\n            }\n\n            try {\n                mTasks.put(task);\n            } catch (InterruptedException e) {\n                e.printStackTrace();\n            }\n        }\n    }\n\n    /**\n     * Pause the queue.\n     *\n     * @see #resume()\n     */\n    public void pause() {\n        synchronized (finishCallback) {\n            if (paused) {\n                FileDownloadLog.w(this, \"require pause this queue(remain %d), but \"\n                        + \"it has already been paused\", mTasks.size());\n                return;\n            }\n\n            paused = true;\n            mTasks.drainTo(pausedList);\n            if (workingTask != null) {\n                workingTask.removeFinishListener(finishCallback);\n                workingTask.pause();\n            }\n        }\n\n    }\n\n    /**\n     * Resume the queue if the queue is paused.\n     *\n     * @see #pause()\n     */\n    public void resume() {\n        synchronized (finishCallback) {\n            if (!paused) {\n                FileDownloadLog.w(this, \"require resume this queue(remain %d), but it is\"\n                        + \" still running\", mTasks.size());\n                return;\n            }\n\n            paused = false;\n            mTasks.addAll(pausedList);\n            pausedList.clear();\n\n            if (workingTask == null) {\n                sendNext();\n            } else {\n                workingTask.addFinishListener(finishCallback);\n                workingTask.start();\n            }\n        }\n    }\n\n    /**\n     * Returns the identify of the working task, if there is task is working, you will receive\n     * {@link #ID_INVALID}.\n     *\n     * @return the identify of the working task\n     */\n    public int getWorkingTaskId() {\n        return workingTask != null ? workingTask.getId() : ID_INVALID;\n    }\n\n    /**\n     * Get the count of tasks which is waiting on this queue.\n     *\n     * @return the count of waiting tasks on this queue.\n     */\n    public int getWaitingTaskCount() {\n        return mTasks.size() + pausedList.size();\n    }\n\n    /**\n     * Attempts to stop the working task, halts the processing of waiting tasks, and returns a list\n     * of the tasks that were awaiting execution. These tasks are drained (removed) from the task\n     * queue upon return from this method.\n     */\n    public List<BaseDownloadTask> shutdown() {\n        synchronized (finishCallback) {\n            if (workingTask != null) {\n                pause();\n            }\n\n            final List<BaseDownloadTask> unDealTaskList = new ArrayList<>(pausedList);\n            pausedList.clear();\n            mHandler.removeMessages(WHAT_NEXT);\n            mHandlerThread.interrupt();\n            mHandlerThread.quit();\n\n            return unDealTaskList;\n        }\n    }\n\n\n    private class SerialLoop implements Handler.Callback {\n\n        @Override\n        public boolean handleMessage(Message msg) {\n            switch (msg.what) {\n                case WHAT_NEXT:\n                    try {\n                        if (paused) break;\n                        workingTask = mTasks.take();\n                        workingTask.addFinishListener(finishCallback)\n                                .start();\n                    } catch (InterruptedException ignored) { }\n                    break;\n                default:\n                    //ignored\n            }\n            return false;\n        }\n    }\n\n    private static class SerialFinishCallback implements BaseDownloadTask.FinishListener {\n        private final WeakReference<FileDownloadSerialQueue> mQueueWeakReference;\n\n        SerialFinishCallback(WeakReference<FileDownloadSerialQueue> queueWeakReference) {\n            this.mQueueWeakReference = queueWeakReference;\n        }\n\n        @Override\n        public synchronized void over(BaseDownloadTask task) {\n            task.removeFinishListener(this);\n\n            if (mQueueWeakReference == null) {\n                return;\n            }\n\n            final FileDownloadSerialQueue queue = mQueueWeakReference.get();\n            if (queue == null) {\n                return;\n            }\n\n            queue.workingTask = null;\n            if (queue.paused) {\n                return;\n            }\n            queue.sendNext();\n        }\n    }\n\n    private void sendNext() {\n        mHandler.sendEmptyMessage(WHAT_NEXT);\n    }\n\n}\n"
  },
  {
    "path": "library/src/main/java/com/liulishuo/filedownloader/util/FileDownloadUtils.java",
    "content": "/*\n * Copyright (c) 2015 LingoChamp Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *    http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.liulishuo.filedownloader.util;\n\nimport android.app.ActivityManager;\nimport android.content.Context;\nimport android.content.pm.PackageManager;\nimport android.net.ConnectivityManager;\nimport android.net.NetworkInfo;\nimport android.os.Build;\nimport android.os.Environment;\nimport android.os.PowerManager;\nimport android.os.StatFs;\nimport android.text.TextUtils;\n\nimport com.liulishuo.filedownloader.BuildConfig;\nimport com.liulishuo.filedownloader.connection.FileDownloadConnection;\nimport com.liulishuo.filedownloader.download.CustomComponentHolder;\nimport com.liulishuo.filedownloader.exception.FileDownloadGiveUpRetryException;\nimport com.liulishuo.filedownloader.exception.FileDownloadSecurityException;\nimport com.liulishuo.filedownloader.model.FileDownloadModel;\nimport com.liulishuo.filedownloader.services.FileDownloadService;\nimport com.liulishuo.filedownloader.stream.FileDownloadOutputStream;\n\nimport java.io.File;\nimport java.io.IOException;\nimport java.io.UnsupportedEncodingException;\nimport java.net.HttpURLConnection;\nimport java.net.MalformedURLException;\nimport java.net.URL;\nimport java.net.URLDecoder;\nimport java.security.MessageDigest;\nimport java.security.NoSuchAlgorithmException;\nimport java.util.List;\nimport java.util.Locale;\nimport java.util.regex.Matcher;\nimport java.util.regex.Pattern;\n\nimport static com.liulishuo.filedownloader.model.FileDownloadModel.TOTAL_VALUE_IN_CHUNKED_RESOURCE;\n\n/**\n * The utils for FileDownloader.\n */\n@SuppressWarnings({\"SameParameterValue\", \"WeakerAccess\"})\npublic class FileDownloadUtils {\n\n    private static int minProgressStep = 65536;\n    private static long minProgressTime = 2000;\n\n    /**\n     * @param minProgressStep The minimum bytes interval in per step to sync to the file and the\n     *                        database.\n     *                        <p>\n     *                        Used for adjudging whether is time to sync the downloaded so far bytes\n     *                        to database and make sure sync the downloaded buffer to local file.\n     *                        <p/>\n     *                        More smaller more frequently, then download more slowly, but will more\n     *                        safer in scene of the process is killed unexpected.\n     *                        <p/>\n     *                        Default 65536, which follow the value in\n     *                        com.android.providers.downloads.Constants.\n     * @see com.liulishuo.filedownloader.download.DownloadStatusCallback#onProgress(long)\n     * @see #setMinProgressTime(long)\n     */\n    public static void setMinProgressStep(int minProgressStep) throws IllegalAccessException {\n        if (isDownloaderProcess(FileDownloadHelper.getAppContext())) {\n            FileDownloadUtils.minProgressStep = minProgressStep;\n        } else {\n            throw new IllegalAccessException(\"This value is used in the :filedownloader process,\"\n                    + \" so set this value in your process is without effect. You can add \"\n                    + \"'process.non-separate=true' in 'filedownloader.properties' to share the main\"\n                    + \" process to FileDownloadService. Or you can configure this value in \"\n                    + \"'filedownloader.properties' by 'download.min-progress-step'.\");\n        }\n    }\n\n    /**\n     * @param minProgressTime The minimum millisecond interval in per time to sync to the file and\n     *                        the database.\n     *                        <p>\n     *                        Used for adjudging whether is time to sync the downloaded so far bytes\n     *                        to database and make sure sync the downloaded buffer to local file.\n     *                        <p/>\n     *                        More smaller more frequently, then download more slowly, but will more\n     *                        safer in scene of the process is killed unexpected.\n     *                        <p/>\n     *                        Default 2000, which follow the value in\n     *                        com.android.providers.downloads.Constants.\n     * @see com.liulishuo.filedownloader.download.DownloadStatusCallback#onProgress(long)\n     * @see #setMinProgressStep(int)\n     */\n    public static void setMinProgressTime(long minProgressTime) throws IllegalAccessException {\n        if (isDownloaderProcess(FileDownloadHelper.getAppContext())) {\n            FileDownloadUtils.minProgressTime = minProgressTime;\n        } else {\n            throw new IllegalAccessException(\"This value is used in the :filedownloader process,\"\n                    + \" so set this value in your process is without effect. You can add \"\n                    + \"'process.non-separate=true' in 'filedownloader.properties' to share the main\"\n                    + \" process to FileDownloadService. Or you can configure this value in \"\n                    + \"'filedownloader.properties' by 'download.min-progress-time'.\");\n        }\n    }\n\n    public static int getMinProgressStep() {\n        return minProgressStep;\n    }\n\n    public static long getMinProgressTime() {\n        return minProgressTime;\n    }\n\n    /**\n     * Checks whether the filename looks legitimate.\n     */\n    @SuppressWarnings({\"SameReturnValue\", \"UnusedParameters\"})\n    public static boolean isFilenameValid(String filename) {\n//        filename = filename.replaceFirst(\"/+\", \"/\"); // normalize leading\n        // slashes\n//        return filename.startsWith(Environment.getDownloadCacheDirectory()\n//                .toString())\n//                || filename.startsWith(Environment\n//                .getExternalStorageDirectory().toString());\n        return true;\n    }\n\n    private static String defaultSaveRootPath;\n\n    public static String getDefaultSaveRootPath() {\n        if (!TextUtils.isEmpty(defaultSaveRootPath)) {\n            return defaultSaveRootPath;\n        }\n\n        boolean useExternalStorage = false;\n        if (FileDownloadHelper.getAppContext().getExternalCacheDir() != null) {\n            if (Environment.getExternalStorageState().equals(\"mounted\")) {\n                if (Environment.getExternalStorageDirectory().getFreeSpace() > 0) {\n                    useExternalStorage = true;\n                }\n            }\n        }\n\n        if (useExternalStorage) {\n            return FileDownloadHelper.getAppContext().getExternalCacheDir().getAbsolutePath();\n        } else {\n            return FileDownloadHelper.getAppContext().getCacheDir().getAbsolutePath();\n        }\n    }\n\n    public static String getDefaultSaveFilePath(final String url) {\n        return generateFilePath(getDefaultSaveRootPath(), generateFileName(url));\n    }\n\n    public static String generateFileName(final String url) {\n        return md5(url);\n    }\n\n    /**\n     * @see #getTargetFilePath(String, boolean, String)\n     */\n    public static String generateFilePath(String directory, String filename) {\n        if (filename == null) {\n            throw new IllegalStateException(\"can't generate real path, the file name is null\");\n        }\n\n        if (directory == null) {\n            throw new IllegalStateException(\"can't generate real path, the directory is null\");\n        }\n\n        return formatString(\"%s%s%s\", directory, File.separator, filename);\n    }\n\n    /**\n     * The path is used as the default directory in the case of the task without set path.\n     *\n     * @param path default root path for save download file.\n     * @see com.liulishuo.filedownloader.BaseDownloadTask#setPath(String, boolean)\n     */\n    public static void setDefaultSaveRootPath(final String path) {\n        defaultSaveRootPath = path;\n    }\n\n    /**\n     * @param targetPath The target path for the download task.\n     * @return The temp path is {@code targetPath} in downloading status; The temp path is used for\n     * storing the file not completed downloaded yet.\n     */\n    public static String getTempPath(final String targetPath) {\n        return FileDownloadUtils.formatString(\"%s.temp\", targetPath);\n    }\n\n    /**\n     * @param url  The downloading URL.\n     * @param path The absolute file path.\n     * @return The download id.\n     */\n    public static int generateId(final String url, final String path) {\n        return CustomComponentHolder.getImpl().getIdGeneratorInstance()\n                .generateId(url, path, false);\n    }\n\n    /**\n     * @param url  The downloading URL.\n     * @param path If {@code pathAsDirectory} is {@code true}, {@code path} would be the absolute\n     *             directory to place the file;\n     *             If {@code pathAsDirectory} is {@code false}, {@code path} would be the absolute\n     *             file path.\n     * @return The download id.\n     */\n    public static int generateId(final String url, final String path,\n                                 final boolean pathAsDirectory) {\n        return CustomComponentHolder.getImpl().getIdGeneratorInstance()\n                .generateId(url, path, pathAsDirectory);\n    }\n\n    public static String md5(String string) {\n        byte[] hash;\n        try {\n            hash = MessageDigest.getInstance(\"MD5\").digest(string.getBytes(\"UTF-8\"));\n        } catch (NoSuchAlgorithmException e) {\n            throw new RuntimeException(\"Huh, MD5 should be supported?\", e);\n        } catch (UnsupportedEncodingException e) {\n            throw new RuntimeException(\"Huh, UTF-8 should be supported?\", e);\n        }\n\n        StringBuilder hex = new StringBuilder(hash.length * 2);\n        for (byte b : hash) {\n            if ((b & 0xFF) < 0x10) hex.append(\"0\");\n            hex.append(Integer.toHexString(b & 0xFF));\n        }\n        return hex.toString();\n    }\n\n\n    public static String getStack() {\n        return getStack(true);\n    }\n\n    public static String getStack(final boolean printLine) {\n        StackTraceElement[] stackTrace = new Throwable().getStackTrace();\n        return getStack(stackTrace, printLine);\n    }\n\n    public static String getStack(final StackTraceElement[] stackTrace, final boolean printLine) {\n        if ((stackTrace == null) || (stackTrace.length < 4)) {\n            return \"\";\n        }\n\n        StringBuilder t = new StringBuilder();\n\n        for (int i = 3; i < stackTrace.length; i++) {\n            if (!stackTrace[i].getClassName().contains(\"com.liulishuo.filedownloader\")) {\n                continue;\n            }\n            t.append(\"[\");\n            t.append(stackTrace[i].getClassName()\n                    .substring(\"com.liulishuo.filedownloader\".length()));\n            t.append(\":\");\n            t.append(stackTrace[i].getMethodName());\n            if (printLine) {\n                t.append(\"(\").append(stackTrace[i].getLineNumber()).append(\")]\");\n            } else {\n                t.append(\"]\");\n            }\n        }\n        return t.toString();\n    }\n\n    private static Boolean isDownloaderProcess;\n\n    /**\n     * @param context the context\n     * @return {@code true} if the FileDownloadService is allowed to run on the current process,\n     * {@code false} otherwise.\n     */\n    public static boolean isDownloaderProcess(final Context context) {\n        if (isDownloaderProcess != null) {\n            return isDownloaderProcess;\n        }\n\n        boolean result = false;\n        do {\n            if (FileDownloadProperties.getImpl().processNonSeparate) {\n                result = true;\n                break;\n            }\n\n            int pid = android.os.Process.myPid();\n            final ActivityManager activityManager = (ActivityManager) context.\n                    getSystemService(Context.ACTIVITY_SERVICE);\n\n            if (activityManager == null) {\n                FileDownloadLog.w(FileDownloadUtils.class, \"fail to get the activity manager!\");\n                return false;\n            }\n\n            final List<ActivityManager.RunningAppProcessInfo> runningAppProcessInfoList =\n                    activityManager.getRunningAppProcesses();\n\n            if (null == runningAppProcessInfoList || runningAppProcessInfoList.isEmpty()) {\n                FileDownloadLog\n                        .w(FileDownloadUtils.class, \"The running app process info list from\"\n                                + \" ActivityManager is null or empty, maybe current App is not \"\n                                + \"running.\");\n                return false;\n            }\n\n            for (ActivityManager.RunningAppProcessInfo processInfo : runningAppProcessInfoList) {\n                if (processInfo.pid == pid) {\n                    result = processInfo.processName.endsWith(\":filedownloader\");\n                    break;\n                }\n            }\n\n        } while (false);\n\n        isDownloaderProcess = result;\n        return isDownloaderProcess;\n    }\n\n    public static String[] convertHeaderString(final String nameAndValuesString) {\n        final String[] lineString = nameAndValuesString.split(\"\\n\");\n        final String[] namesAndValues = new String[lineString.length * 2];\n\n        for (int i = 0; i < lineString.length; i++) {\n            final String[] nameAndValue = lineString[i].split(\": \");\n            /**\n             * @see Headers#toString()\n             * @see Headers#name(int)\n             * @see Headers#value(int)\n             */\n            namesAndValues[i * 2] = nameAndValue[0];\n            namesAndValues[i * 2 + 1] = nameAndValue[1];\n        }\n\n        return namesAndValues;\n    }\n\n    public static long getFreeSpaceBytes(final String path) {\n        long freeSpaceBytes;\n        final StatFs statFs = new StatFs(path);\n        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR2) {\n            freeSpaceBytes = statFs.getAvailableBytes();\n        } else {\n            //noinspection deprecation\n            freeSpaceBytes = statFs.getAvailableBlocks() * (long) statFs.getBlockSize();\n        }\n\n        return freeSpaceBytes;\n    }\n\n    public static String formatString(final String msg, Object... args) {\n        return String.format(Locale.ENGLISH, msg, args);\n    }\n\n    private static final String INTERNAL_DOCUMENT_NAME = \"filedownloader\";\n    private static final String OLD_FILE_CONVERTED_FILE_NAME = \".old_file_converted\";\n\n    @SuppressWarnings(\"ResultOfMethodCallIgnored\")\n    public static void markConverted(final Context context) {\n        final File file = getConvertedMarkedFile(context);\n        try {\n            file.getParentFile().mkdirs();\n            file.createNewFile();\n        } catch (IOException e) {\n            e.printStackTrace();\n        }\n    }\n\n    private static Boolean filenameConverted = null;\n\n    /**\n     * @return Whether has converted all files' name from 'filename'(in old architecture) to\n     * 'filename.temp', if it's in downloading state.\n     * <p>\n     * If {@code true}, You can check whether the file has completed downloading with\n     * {@link File#exists()} directly.\n     * <p>\n     * when {@link FileDownloadService#onCreate()} is invoked, This value will be assigned to\n     * {@code true} only once since you upgrade the filedownloader version to 0.3.3 or higher.\n     */\n    public static boolean isFilenameConverted(final Context context) {\n        if (filenameConverted == null) {\n            filenameConverted = getConvertedMarkedFile(context).exists();\n        }\n\n        return filenameConverted;\n    }\n\n    public static File getConvertedMarkedFile(final Context context) {\n        return new File(context.getFilesDir().getAbsolutePath() + File.separator\n                + INTERNAL_DOCUMENT_NAME, OLD_FILE_CONVERTED_FILE_NAME);\n    }\n\n    // note on https://tools.ietf.org/html/rfc5987\n    private static final Pattern CONTENT_DISPOSITION_WITH_ASTERISK_PATTERN =\n            Pattern.compile(\"attachment;\\\\s*filename\\\\*\\\\s*=\\\\s*\\\"*([^\\\"]*)'\\\\S*'([^\\\"]*)\\\"*\");\n    // note on http://www.ietf.org/rfc/rfc1806.txt\n    private static final Pattern CONTENT_DISPOSITION_WITHOUT_ASTERISK_PATTERN =\n            Pattern.compile(\"attachment;\\\\s*filename\\\\s*=\\\\s*\\\"*([^\\\"\\\\n]*)\\\"*\");\n\n    public static long parseContentRangeFoInstanceLength(String contentRange) {\n        if (contentRange == null) return -1;\n\n        final String[] session = contentRange.split(\"/\");\n        if (session.length >= 2) {\n            try {\n                return Long.parseLong(session[1]);\n            } catch (NumberFormatException e) {\n                FileDownloadLog.w(FileDownloadUtils.class, \"parse instance length failed with %s\",\n                        contentRange);\n            }\n        }\n\n        return -1;\n    }\n\n    /**\n     * The same to com.android.providers.downloads.Helpers#parseContentDisposition.\n     * </p>\n     * Parse the Content-Disposition HTTP Header. The format of the header\n     * is defined here: http://www.w3.org/Protocols/rfc2616/rfc2616-sec19.html\n     * This header provides a filename for content that is going to be\n     * downloaded to the file system. We only support the attachment type.\n     */\n    public static String parseContentDisposition(String contentDisposition) {\n        if (contentDisposition == null) {\n            return null;\n        }\n\n        try {\n            Matcher m = CONTENT_DISPOSITION_WITH_ASTERISK_PATTERN.matcher(contentDisposition);\n            if (m.find()) {\n                String charset = m.group(1);\n                String encodeFileName = m.group(2);\n                return URLDecoder.decode(encodeFileName, charset);\n            }\n\n            m = CONTENT_DISPOSITION_WITHOUT_ASTERISK_PATTERN.matcher(contentDisposition);\n            if (m.find()) {\n                return m.group(1);\n            }\n        } catch (IllegalStateException | UnsupportedEncodingException ignore) {\n            // This function is defined as returning null when it can't parse the header\n        }\n        return null;\n    }\n\n    /**\n     * @param path            If {@code pathAsDirectory} is true, the {@code path} would be the\n     *                        absolute directory to settle down the file;\n     *                        If {@code pathAsDirectory} is false, the {@code path} would be the\n     *                        absolute file path.\n     * @param pathAsDirectory whether the {@code path} is a directory.\n     * @param filename        the file's name.\n     * @return the absolute path of the file. If can't find by params, will return {@code null}.\n     */\n    public static String getTargetFilePath(String path, boolean pathAsDirectory, String filename) {\n        if (path == null) {\n            return null;\n        }\n\n        if (pathAsDirectory) {\n            if (filename == null) {\n                return null;\n            }\n\n            return FileDownloadUtils.generateFilePath(path, filename);\n        } else {\n            return path;\n        }\n    }\n\n    /**\n     * The same to {@link File#getParent()}, for non-creating a file object.\n     *\n     * @return this file's parent pathname or {@code null}.\n     */\n    public static String getParent(final String path) {\n        int length = path.length(), firstInPath = 0;\n        if (File.separatorChar == '\\\\' && length > 2 && path.charAt(1) == ':') {\n            firstInPath = 2;\n        }\n        int index = path.lastIndexOf(File.separatorChar);\n        if (index == -1 && firstInPath > 0) {\n            index = 2;\n        }\n        if (index == -1 || path.charAt(length - 1) == File.separatorChar) {\n            return null;\n        }\n        if (path.indexOf(File.separatorChar) == index\n                && path.charAt(firstInPath) == File.separatorChar) {\n            return path.substring(0, index + 1);\n        }\n        return path.substring(0, index);\n    }\n\n    private static final String FILEDOWNLOADER_PREFIX = \"FileDownloader\";\n\n    public static String getThreadPoolName(String name) {\n        return FILEDOWNLOADER_PREFIX + \"-\" + name;\n    }\n\n    public static boolean isNetworkNotOnWifiType() {\n        final ConnectivityManager manager = (ConnectivityManager) FileDownloadHelper.getAppContext()\n                .\n                        getSystemService(Context.CONNECTIVITY_SERVICE);\n\n        if (manager == null) {\n            FileDownloadLog.w(FileDownloadUtils.class, \"failed to get connectivity manager!\");\n            return true;\n        }\n\n        //noinspection MissingPermission, because we check permission accessable when invoked\n        final NetworkInfo info = manager.getActiveNetworkInfo();\n\n        return info == null || info.getType() != ConnectivityManager.TYPE_WIFI;\n    }\n\n    public static boolean checkPermission(String permission) {\n        final int perm = FileDownloadHelper.getAppContext()\n                .checkCallingOrSelfPermission(permission);\n        return perm == PackageManager.PERMISSION_GRANTED;\n    }\n\n    public static long convertContentLengthString(String s) {\n        if (s == null) return -1;\n        try {\n            return Long.parseLong(s);\n        } catch (NumberFormatException e) {\n            return -1;\n        }\n    }\n\n    public static String findEtag(final int id, FileDownloadConnection connection) {\n        if (connection == null) {\n            throw new RuntimeException(\"connection is null when findEtag\");\n        }\n\n        final String newEtag = connection.getResponseHeaderField(\"Etag\");\n\n        if (FileDownloadLog.NEED_LOG) {\n            FileDownloadLog.d(FileDownloadUtils.class, \"etag find %s for task(%d)\", newEtag, id);\n        }\n\n        return newEtag;\n    }\n\n    // accept range is effect by  response code and Accept-Ranges header field.\n    public static boolean isAcceptRange(int responseCode, FileDownloadConnection connection) {\n        if (responseCode == HttpURLConnection.HTTP_PARTIAL\n                || responseCode == FileDownloadConnection.RESPONSE_CODE_FROM_OFFSET) return true;\n\n        final String acceptRanges = connection.getResponseHeaderField(\"Accept-Ranges\");\n        return \"bytes\".equals(acceptRanges);\n    }\n\n    // because of we using one of two HEAD method to request or using range:0-0 to trial connection\n    // only if connection api not support, so we test content-range first and then test\n    // content-length.\n    public static long findInstanceLengthForTrial(FileDownloadConnection connection) {\n        long length = findInstanceLengthFromContentRange(connection);\n        if (length < 0) {\n            length = TOTAL_VALUE_IN_CHUNKED_RESOURCE;\n            FileDownloadLog.w(FileDownloadUtils.class, \"don't get instance length from\"\n                    + \"Content-Range header\");\n        }\n        // the response of HEAD method is not very canonical sometimes(it depends on server\n        // implementation)\n        // so that it's uncertain the content-length is the same as the response of GET method if\n        // content-length=0, so we have to filter this case in here.\n        if (length == 0 && FileDownloadProperties.getImpl().trialConnectionHeadMethod) {\n            length = TOTAL_VALUE_IN_CHUNKED_RESOURCE;\n        }\n\n        return length;\n    }\n\n    public static long findInstanceLengthFromContentRange(FileDownloadConnection connection) {\n        return parseContentRangeFoInstanceLength(getContentRangeHeader(connection));\n    }\n\n    private static String getContentRangeHeader(FileDownloadConnection connection) {\n        return connection.getResponseHeaderField(\"Content-Range\");\n    }\n\n    public static long findContentLength(final int id, FileDownloadConnection connection) {\n        long contentLength = convertContentLengthString(\n                connection.getResponseHeaderField(\"Content-Length\"));\n        final String transferEncoding = connection.getResponseHeaderField(\"Transfer-Encoding\");\n\n        if (contentLength < 0) {\n            final boolean isEncodingChunked = transferEncoding != null && transferEncoding\n                    .equals(\"chunked\");\n            if (!isEncodingChunked) {\n                // not chunked transfer encoding data\n                if (FileDownloadProperties.getImpl().httpLenient) {\n                    // do not response content-length either not chunk transfer encoding,\n                    // but HTTP lenient is true, so handle as the case of transfer encoding chunk\n                    contentLength = TOTAL_VALUE_IN_CHUNKED_RESOURCE;\n                    if (FileDownloadLog.NEED_LOG) {\n                        FileDownloadLog\n                                .d(FileDownloadUtils.class, \"%d response header is not legal but \"\n                                        + \"HTTP lenient is true, so handle as the case of \"\n                                        + \"transfer encoding chunk\", id);\n                    }\n                } else {\n                    throw new FileDownloadGiveUpRetryException(\"can't know the size of the \"\n                            + \"download file, and its Transfer-Encoding is not Chunked \"\n                            + \"either.\\nyou can ignore such exception by add \"\n                            + \"http.lenient=true to the filedownloader.properties\");\n                }\n            } else {\n                contentLength = TOTAL_VALUE_IN_CHUNKED_RESOURCE;\n            }\n        }\n\n        return contentLength;\n    }\n\n    public static long findContentLengthFromContentRange(FileDownloadConnection connection) {\n        final String contentRange = getContentRangeHeader(connection);\n        long contentLength = parseContentLengthFromContentRange(contentRange);\n        if (contentLength < 0) contentLength = TOTAL_VALUE_IN_CHUNKED_RESOURCE;\n        return contentLength;\n    }\n\n    public static long parseContentLengthFromContentRange(String contentRange) {\n        if (contentRange == null || contentRange.length() == 0) return -1;\n        final String pattern = \"bytes (\\\\d+)-(\\\\d+)/\\\\d+\";\n        try {\n            final Pattern r = Pattern.compile(pattern);\n            final Matcher m = r.matcher(contentRange);\n            if (m.find()) {\n                final long rangeStart = Long.parseLong(m.group(1));\n                final long rangeEnd = Long.parseLong(m.group(2));\n                return rangeEnd - rangeStart + 1;\n            }\n        } catch (Exception e) {\n            FileDownloadLog.e(FileDownloadUtils.class, e, \"parse content length\"\n                    + \" from content range error\");\n        }\n        return -1;\n    }\n\n    public static String findFilename(FileDownloadConnection connection, String url)\n            throws FileDownloadSecurityException {\n        String filename = FileDownloadUtils.parseContentDisposition(connection.\n                getResponseHeaderField(\"Content-Disposition\"));\n\n        if (TextUtils.isEmpty(filename)) {\n            filename = findFileNameFromUrl(url);\n        }\n\n        if (TextUtils.isEmpty(filename)) {\n            filename = FileDownloadUtils.generateFileName(url);\n        } else if (filename.contains(\"../\")) {\n            throw new FileDownloadSecurityException(FileDownloadUtils.formatString(\n                    \"The filename [%s] from the response is not allowable, because it contains \"\n                            + \"'../', which can raise the directory traversal vulnerability\",\n                    filename));\n        }\n\n        return filename;\n    }\n\n    public static FileDownloadOutputStream createOutputStream(final String path)\n            throws IOException {\n\n        if (TextUtils.isEmpty(path)) {\n            throw new RuntimeException(\"found invalid internal destination path, empty\");\n        }\n\n        //noinspection ConstantConditions\n        if (!FileDownloadUtils.isFilenameValid(path)) {\n            throw new RuntimeException(\n                    FileDownloadUtils.formatString(\"found invalid internal destination filename\"\n                            + \" %s\", path));\n        }\n\n        File file = new File(path);\n\n        if (file.exists() && file.isDirectory()) {\n            throw new RuntimeException(\n                    FileDownloadUtils.formatString(\"found invalid internal destination path[%s],\"\n                            + \" & path is directory[%B]\", path, file.isDirectory()));\n        }\n        if (!file.exists()) {\n            if (!file.createNewFile()) {\n                throw new IOException(\n                        FileDownloadUtils.formatString(\"create new file error  %s\",\n                                file.getAbsolutePath()));\n            }\n        }\n\n        return CustomComponentHolder.getImpl().createOutputStream(file);\n    }\n\n    public static boolean isBreakpointAvailable(final int id, final FileDownloadModel model) {\n        return isBreakpointAvailable(id, model, null);\n    }\n\n    /**\n     * @return can resume by break point\n     */\n    public static boolean isBreakpointAvailable(final int id, final FileDownloadModel model,\n                                                final Boolean outputStreamSupportSeek) {\n        if (model == null) {\n            if (FileDownloadLog.NEED_LOG) {\n                FileDownloadLog.d(FileDownloadUtils.class, \"can't continue %d model == null\", id);\n            }\n            return false;\n        }\n\n        if (model.getTempFilePath() == null) {\n            if (FileDownloadLog.NEED_LOG) {\n                FileDownloadLog\n                        .d(FileDownloadUtils.class, \"can't continue %d temp path == null\", id);\n            }\n            return false;\n        }\n\n        return isBreakpointAvailable(id, model, model.getTempFilePath(), outputStreamSupportSeek);\n    }\n\n    public static boolean isBreakpointAvailable(final int id, final FileDownloadModel model,\n                                                final String path,\n                                                final Boolean outputStreamSupportSeek) {\n        boolean result = false;\n\n        do {\n            if (path == null) {\n                if (FileDownloadLog.NEED_LOG) {\n                    FileDownloadLog.d(FileDownloadUtils.class, \"can't continue %d path = null\", id);\n                }\n                break;\n            }\n\n            File file = new File(path);\n            final boolean isExists = file.exists();\n            final boolean isDirectory = file.isDirectory();\n\n            if (!isExists || isDirectory) {\n                if (FileDownloadLog.NEED_LOG) {\n                    FileDownloadLog.d(FileDownloadUtils.class,\n                            \"can't continue %d file not suit, exists[%B], directory[%B]\",\n                            id, isExists, isDirectory);\n                }\n                break;\n            }\n\n            final long fileLength = file.length();\n            final long currentOffset = model.getSoFar();\n\n            if (model.getConnectionCount() <= 1 && currentOffset == 0) {\n                // the sofar is stored on connection table\n                if (FileDownloadLog.NEED_LOG) {\n                    FileDownloadLog.d(FileDownloadUtils.class,\n                            \"can't continue %d the downloaded-record is zero.\",\n                            id);\n                }\n                break;\n            }\n\n            final long totalLength = model.getTotal();\n            if (fileLength < currentOffset\n                    || (totalLength != TOTAL_VALUE_IN_CHUNKED_RESOURCE  // not chunk transfer\n                    && (fileLength > totalLength || currentOffset >= totalLength))\n            ) {\n                // dirty data.\n                if (FileDownloadLog.NEED_LOG) {\n                    FileDownloadLog.d(FileDownloadUtils.class, \"can't continue %d dirty data\"\n                                    + \" fileLength[%d] sofar[%d] total[%d]\",\n                            id, fileLength, currentOffset, totalLength);\n                }\n                break;\n            }\n\n            if (outputStreamSupportSeek != null && !outputStreamSupportSeek\n                    && totalLength == fileLength) {\n                if (FileDownloadLog.NEED_LOG) {\n                    FileDownloadLog.d(FileDownloadUtils.class, \"can't continue %d, because of the \"\n                                    + \"output stream doesn't support seek, but the task has \"\n                                    + \"already pre-allocated, so we only can download it from the\"\n                                    + \" very beginning.\",\n                            id);\n                }\n                break;\n            }\n\n            result = true;\n        } while (false);\n\n\n        return result;\n    }\n\n    public static void deleteTaskFiles(String targetFilepath, String tempFilePath) {\n        deleteTempFile(tempFilePath);\n        deleteTargetFile(targetFilepath);\n    }\n\n    public static void deleteTempFile(String tempFilePath) {\n        if (tempFilePath != null) {\n            final File tempFile = new File(tempFilePath);\n            if (tempFile.exists()) {\n                //noinspection ResultOfMethodCallIgnored\n                tempFile.delete();\n            }\n        }\n    }\n\n    public static void deleteTargetFile(String targetFilePath) {\n        if (targetFilePath != null) {\n            final File targetFile = new File(targetFilePath);\n            if (targetFile.exists()) {\n                //noinspection ResultOfMethodCallIgnored\n                targetFile.delete();\n            }\n        }\n    }\n\n    public static boolean isNeedSync(long bytesDelta, long timestampDelta) {\n        return bytesDelta > FileDownloadUtils.getMinProgressStep()\n                && timestampDelta > FileDownloadUtils.getMinProgressTime();\n    }\n\n    public static String defaultUserAgent() {\n        return formatString(\"FileDownloader/%s\", BuildConfig.VERSION_NAME);\n    }\n\n    private static boolean isAppOnForeground(Context context) {\n        ActivityManager activityManager = (ActivityManager) context.getApplicationContext()\n                .getSystemService(Context.ACTIVITY_SERVICE);\n        if (activityManager == null) return false;\n\n        List<ActivityManager.RunningAppProcessInfo> appProcesses =\n                activityManager.getRunningAppProcesses();\n        if (appProcesses == null) return false;\n\n        PowerManager pm = (PowerManager) context.getSystemService(Context.POWER_SERVICE);\n        if (pm == null) return false;\n        if (Build.VERSION.SDK_INT > Build.VERSION_CODES.KITKAT) {\n            if (!pm.isInteractive()) return false;\n        } else {\n            if (!pm.isScreenOn()) return false;\n        }\n\n        String packageName = context.getApplicationContext().getPackageName();\n        for (ActivityManager.RunningAppProcessInfo appProcess : appProcesses) {\n            // The name of the process that this object is associated with.\n            if (appProcess.processName.equals(packageName) && appProcess.importance\n                    == ActivityManager.RunningAppProcessInfo.IMPORTANCE_FOREGROUND) {\n                return true;\n            }\n\n        }\n        return false;\n    }\n\n    public static boolean needMakeServiceForeground(Context context) {\n        return Build.VERSION.SDK_INT >= Build.VERSION_CODES.O && !isAppOnForeground(context);\n    }\n\n    static String findFileNameFromUrl(String url) {\n        if (url == null || url.isEmpty()) {\n            return null;\n        }\n        try {\n            final URL parseUrl = new URL(url);\n            final String path = parseUrl.getPath();\n            String fileName = path.substring(path.lastIndexOf('/') + 1);\n            if (fileName.isEmpty()) return null;\n            return fileName;\n        } catch (MalformedURLException ignore) {\n        }\n        return null;\n    }\n}\n"
  },
  {
    "path": "library/src/main/res/values/strings.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n    <string name=\"default_filedownloader_notification_title\">FileDownloader</string>\n    <string name=\"default_filedownloader_notification_content\">FileDownloader is running.</string>\n</resources>"
  },
  {
    "path": "library/src/test/java/com/liulishuo/filedownloader/FileDownloaderTest.java",
    "content": "/*\n * Copyright (c) 2015 LingoChamp Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *   http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.liulishuo.filedownloader;\n\nimport com.liulishuo.filedownloader.download.CustomComponentHolder;\nimport com.liulishuo.filedownloader.util.FileDownloadHelper;\n\nimport junit.framework.Assert;\n\nimport org.junit.Test;\nimport org.junit.runner.RunWith;\nimport org.robolectric.RobolectricTestRunner;\n\nimport static org.robolectric.RuntimeEnvironment.application;\n\n@RunWith(RobolectricTestRunner.class)\npublic class FileDownloaderTest {\n\n    @Test\n    public void setup_withContext_hold() {\n        FileDownloader.setup(application);\n\n        Assert.assertEquals(application.getApplicationContext(),\n                FileDownloadHelper.getAppContext());\n    }\n\n    @Test\n    public void setupOnApplicationOnCreate_withContext_hold() {\n        FileDownloader.setupOnApplicationOnCreate(application);\n\n        Assert.assertEquals(application.getApplicationContext(),\n                FileDownloadHelper.getAppContext());\n    }\n\n    @Test\n    public void setupOnApplicationOnCreate_InitCustomMaker_valid() {\n        FileDownloader.setupOnApplicationOnCreate(application)\n                .maxNetworkThreadCount(6)\n                .commit();\n\n        Assert.assertEquals(CustomComponentHolder.getImpl().getMaxNetworkThreadCount(), 6);\n    }\n}"
  },
  {
    "path": "library/src/test/java/com/liulishuo/filedownloader/connection/FileDownloadUrlConnectionTest.java",
    "content": "/*\n * Copyright (c) 2015 LingoChamp Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *   http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.liulishuo.filedownloader.connection;\n\nimport org.junit.Before;\nimport org.junit.Test;\nimport org.mockito.Mock;\nimport org.mockito.Mockito;\n\nimport java.io.IOException;\nimport java.net.Proxy;\nimport java.net.URL;\nimport java.net.URLConnection;\n\nimport static org.mockito.Matchers.anyInt;\nimport static org.mockito.Mockito.times;\nimport static org.mockito.Mockito.verify;\nimport static org.mockito.MockitoAnnotations.initMocks;\n\n@SuppressWarnings(\"CanBeFinal\")\npublic class FileDownloadUrlConnectionTest {\n\n    @Mock\n    private Proxy mProxy;\n\n    @Mock\n    private URLConnection mConnection;\n\n    @Mock\n    private URL mURL;\n\n    @Before\n    public void setUp() throws Exception {\n        initMocks(this);\n\n        Mockito.when(mURL.openConnection()).thenReturn(mConnection);\n        Mockito.when(mURL.openConnection(mProxy)).thenReturn(mConnection);\n    }\n\n    @Test\n    public void construct_noConfiguration_noAssigned() throws IOException {\n        FileDownloadUrlConnection.Creator creator = new FileDownloadUrlConnection.Creator();\n\n        creator.create(\"http://blog.dreamtobe.cn\");\n\n        verify(mConnection, times(0)).setConnectTimeout(anyInt());\n        verify(mConnection, times(0)).setReadTimeout(anyInt());\n    }\n\n    @Test\n    public void construct_validConfiguration_Assigned() throws IOException {\n\n\n        FileDownloadUrlConnection.Creator creator = new FileDownloadUrlConnection.Creator(\n                new FileDownloadUrlConnection.Configuration()\n                        .proxy(mProxy)\n                        .connectTimeout(1001)\n                        .readTimeout(1002)\n        );\n\n        creator.create(mURL);\n\n        verify(mURL).openConnection(mProxy);\n        verify(mConnection).setConnectTimeout(1001);\n        verify(mConnection).setReadTimeout(1002);\n    }\n\n\n}"
  },
  {
    "path": "library/src/test/java/com/liulishuo/filedownloader/download/DownloadLaunchRunnableTest.java",
    "content": "/*\n * Copyright (c) 2015 LingoChamp Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *   http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.liulishuo.filedownloader.download;\n\nimport android.app.Application;\nimport android.content.Context;\nimport android.content.pm.PackageManager;\nimport android.net.ConnectivityManager;\n\nimport com.liulishuo.filedownloader.FileDownloader;\nimport com.liulishuo.filedownloader.IThreadPoolMonitor;\nimport com.liulishuo.filedownloader.database.FileDownloadDatabase;\nimport com.liulishuo.filedownloader.exception.FileDownloadNetworkPolicyException;\nimport com.liulishuo.filedownloader.model.FileDownloadHeader;\nimport com.liulishuo.filedownloader.model.FileDownloadModel;\nimport com.liulishuo.filedownloader.model.FileDownloadStatus;\nimport com.liulishuo.filedownloader.util.FileDownloadHelper;\n\nimport org.junit.Test;\nimport org.junit.runner.RunWith;\nimport org.robolectric.RobolectricTestRunner;\nimport org.robolectric.annotation.Config;\n\nimport java.util.Iterator;\n\nimport static org.assertj.core.api.Assertions.assertThat;\nimport static org.mockito.ArgumentMatchers.any;\nimport static org.mockito.ArgumentMatchers.anyString;\nimport static org.mockito.Mockito.mock;\nimport static org.mockito.Mockito.spy;\nimport static org.mockito.Mockito.verify;\nimport static org.mockito.Mockito.when;\nimport static org.robolectric.RuntimeEnvironment.application;\n\n@RunWith(RobolectricTestRunner.class)\n@Config(manifest = Config.NONE)\npublic class DownloadLaunchRunnableTest {\n\n    @Test\n    public void onRetry_validRetryTimesDecrease_only1() {\n        final DownloadLaunchRunnable launchRunnable = DownloadLaunchRunnable\n                .createForTest(mock(DownloadStatusCallback.class),\n                        mock(FileDownloadModel.class), mock(FileDownloadHeader.class),\n                        mock(IThreadPoolMonitor.class),\n                        1000, 100, false,\n                        true, 3);\n        launchRunnable.onRetry(mock(Exception.class));\n        assertThat(launchRunnable.validRetryTimes).isEqualTo(2);\n        launchRunnable.onRetry(mock(Exception.class));\n        assertThat(launchRunnable.validRetryTimes).isEqualTo(1);\n        launchRunnable.onRetry(mock(Exception.class));\n        assertThat(launchRunnable.validRetryTimes).isEqualTo(0);\n    }\n\n    @Test\n    public void run_noWifiButRequired_callbackNetworkError() {\n        // init context\n        final Application spyApplication = spy(application);\n        when(spyApplication.getApplicationContext()).thenReturn(spyApplication);\n        FileDownloader.setupOnApplicationOnCreate(spyApplication)\n                .database(getMockNonOptDatabaseMaker())\n                .commit();\n\n        // no wifi state\n        mockContextNoWifiState(spyApplication);\n\n        // pending model\n        final FileDownloadModel model = mock(FileDownloadModel.class);\n        when(model.getId()).thenReturn(1);\n        when(model.getStatus()).thenReturn(FileDownloadStatus.pending);\n\n        // mock launch runnable.\n        final DownloadStatusCallback callback = mock(DownloadStatusCallback.class);\n        final DownloadLaunchRunnable launchRunnable = DownloadLaunchRunnable.createForTest(callback,\n                model, mock(FileDownloadHeader.class), mock(IThreadPoolMonitor.class),\n                1000, 100, false,\n                true, 0);\n\n\n        launchRunnable.run();\n\n        verify(callback).onErrorDirectly(any(FileDownloadNetworkPolicyException.class));\n    }\n\n\n    private static void mockContextNoWifiState(Context context) {\n        when(context.checkCallingOrSelfPermission(anyString()))\n                .thenReturn(PackageManager.PERMISSION_GRANTED);\n        final ConnectivityManager connectivityManager = mock(ConnectivityManager.class);\n        when(context.getSystemService(Context.CONNECTIVITY_SERVICE))\n                .thenReturn(connectivityManager);\n\n        // not wifi.\n        when(connectivityManager.getActiveNetworkInfo()).thenReturn(null);\n    }\n\n    private static FileDownloadHelper.DatabaseCustomMaker getMockNonOptDatabaseMaker() {\n        final FileDownloadDatabase database = mock(FileDownloadDatabase.class);\n        when(database.maintainer()).thenReturn(new FileDownloadDatabase.Maintainer() {\n            @Override\n            public void onFinishMaintain() {\n            }\n\n            @Override\n            public void onRemovedInvalidData(FileDownloadModel model) {\n            }\n\n            @Override\n            public void onRefreshedValidData(FileDownloadModel model) {\n            }\n\n            @Override\n            public void changeFileDownloadModelId(int oldId, FileDownloadModel modelWithNewId) {\n            }\n\n            @Override\n            public Iterator<FileDownloadModel> iterator() {\n                return new Iterator<FileDownloadModel>() {\n                    @Override\n                    public boolean hasNext() {\n                        return false;\n                    }\n\n                    @Override\n                    public FileDownloadModel next() {\n                        return null;\n                    }\n                };\n            }\n        });\n\n        return new FileDownloadHelper.DatabaseCustomMaker() {\n            @Override\n            public FileDownloadDatabase customMake() {\n                return database;\n            }\n        };\n    }\n}"
  },
  {
    "path": "library/src/test/java/com/liulishuo/filedownloader/download/DownloadRunnableTest.java",
    "content": "/*\n * Copyright (c) 2015 LingoChamp Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *   http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.liulishuo.filedownloader.download;\n\nimport com.liulishuo.filedownloader.connection.FileDownloadConnection;\n\nimport org.junit.Before;\nimport org.junit.Test;\nimport org.junit.runner.RunWith;\nimport org.mockito.Mock;\nimport org.robolectric.RobolectricTestRunner;\n\nimport java.io.IOException;\nimport java.net.HttpURLConnection;\n\nimport static org.mockito.ArgumentMatchers.any;\nimport static org.mockito.Mockito.mock;\nimport static org.mockito.Mockito.spy;\nimport static org.mockito.Mockito.verify;\nimport static org.mockito.Mockito.when;\nimport static org.mockito.MockitoAnnotations.initMocks;\n\n@SuppressWarnings(\"ALL\")\n@RunWith(RobolectricTestRunner.class)\npublic class DownloadRunnableTest {\n\n    @Mock\n    private ConnectTask mockConnectTask;\n\n    private ProcessCallback mockCallback;\n\n    @SuppressWarnings(\"ThrowableInstanceNeverThrown\")\n    private Exception mockIOException = new IOException(\"test\");\n\n    private DownloadRunnable downloadRunnable;\n\n    @Before\n    public void setUp() {\n        initMocks(this);\n\n        mockCallback = spy(new MockProcessCallback());\n        downloadRunnable = new DownloadRunnable.Builder()\n                .setCallback(mockCallback)\n                .buildForTest(mockConnectTask);\n\n        when(mockConnectTask.getProfile()).thenReturn(mock(ConnectionProfile.class));\n    }\n\n    @Test\n    public void run_withConnectFailed_retry() throws IOException, IllegalAccessException {\n        when(mockConnectTask.connect()).thenThrow(mockIOException);\n\n        downloadRunnable.run();\n\n        verify(mockCallback).onRetry(mockIOException);\n    }\n\n    @Test\n    public void run_responseCodeNotMet_error() throws IOException, IllegalAccessException {\n        final FileDownloadConnection connection = mock(FileDownloadConnection.class);\n        when(connection.getResponseCode()).thenReturn(HttpURLConnection.HTTP_PRECON_FAILED);\n        when(mockConnectTask.connect()).thenReturn(connection);\n\n        downloadRunnable.run();\n\n        // retry first.\n        verify(mockCallback).onRetry(any(Exception.class));\n\n        // then callback error.\n        verify(mockCallback).onError(any(Exception.class));\n    }\n\n    private static class MockProcessCallback implements ProcessCallback {\n\n        @Override\n        public void onProgress(long increaseBytes) {\n        }\n\n        @Override\n        public void onCompleted(DownloadRunnable doneRunnable, long startOffset, long endOffset) {\n        }\n\n        boolean isFirstTime = true;\n\n        @Override\n        public boolean isRetry(Exception exception) {\n            if (isFirstTime) {\n                isFirstTime = false;\n                return true;\n            }\n\n            return false;\n        }\n\n        @Override\n        public void onError(Exception exception) {\n        }\n\n\n        @Override\n        public void onRetry(Exception exception) {\n        }\n\n        @Override\n        public void syncProgressFromCache() {\n        }\n    }\n}"
  },
  {
    "path": "library/src/test/java/com/liulishuo/filedownloader/util/FileDownloadUtilsTest.java",
    "content": "/*\n * Copyright (c) 2015 LingoChamp Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *   http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.liulishuo.filedownloader.util;\n\nimport com.liulishuo.filedownloader.connection.FileDownloadConnection;\nimport com.liulishuo.filedownloader.exception.FileDownloadSecurityException;\n\nimport org.junit.Rule;\nimport org.junit.Test;\nimport org.junit.rules.ExpectedException;\nimport org.junit.runner.RunWith;\nimport org.robolectric.RobolectricTestRunner;\nimport org.robolectric.annotation.Config;\n\nimport static org.assertj.core.api.Java6Assertions.assertThat;\nimport static org.mockito.Mockito.mock;\nimport static org.mockito.Mockito.when;\n\n@RunWith(RobolectricTestRunner.class)\n@Config(manifest = Config.NONE)\npublic class FileDownloadUtilsTest {\n\n    @Test\n    public void parseContentDisposition() {\n        String filename = FileDownloadUtils\n                .parseContentDisposition(\"attachment; ...filename ii=\\\"hello world\\\"\");\n        assertThat(filename).isNull();\n        filename = FileDownloadUtils\n                .parseContentDisposition(\"attachment; filename=\\\"hello world\\\"\");\n        assertThat(filename).isEqualTo(\"hello world\");\n        filename = FileDownloadUtils\n                .parseContentDisposition(\"attachment; filename=genome.jpeg\\nabc\");\n        assertThat(filename).isEqualTo(\"genome.jpeg\");\n        filename = FileDownloadUtils\n                .parseContentDisposition(\n                        \"attachment; filename*=\\\"gb2312''%d4%b4%ce%c4%bc%fe.mp3\\\"\");\n        assertThat(filename).isEqualTo(\"源文件.mp3\");\n        filename = FileDownloadUtils\n                .parseContentDisposition(\"attachment; filename*=gb2312''%d4%b4%ce%c4%bc%fe.mp3\");\n        assertThat(filename).isEqualTo(\"源文件.mp3\");\n        filename = FileDownloadUtils\n                .parseContentDisposition(\n                        \"attachment; filename*=\\\"UTF-8''%e6%ba%90%e6%96%87%e4%bb%b6.mp3\\\"\");\n        assertThat(filename).isEqualTo(\"源文件.mp3\");\n        filename = FileDownloadUtils\n                .parseContentDisposition(\"attachment;filename*=\\\"UTF8''1.mp3\\\"\");\n        assertThat(filename).isEqualTo(\"1.mp3\");\n    }\n\n    @Test\n    public void parseContentLengthFromContentRange_withNullContentRange() {\n        long length = FileDownloadUtils.parseContentLengthFromContentRange(null);\n        assertThat(length).isEqualTo(-1);\n    }\n\n    @Test\n    public void parseContentLengthFromContentRange_withEmptyContentRange() {\n        long length = FileDownloadUtils.parseContentLengthFromContentRange(\"\");\n        assertThat(length).isEqualTo(-1);\n    }\n\n    @Test\n    public void parseContentLengthFromContentRange_withStartToEndRange() {\n        long length = FileDownloadUtils\n                .parseContentLengthFromContentRange(\"bytes 25086300-37629450/37629451\");\n        assertThat(length).isEqualTo(12543151);\n    }\n\n    @Test\n    public void parseContentLengthFromContentRange_withUnavailableContentRange() {\n        long length = FileDownloadUtils.parseContentLengthFromContentRange(\"bytes 0-/37629451\");\n        assertThat(length).isEqualTo(-1);\n    }\n\n    @Rule\n    public ExpectedException thrown = ExpectedException.none();\n\n    @Test\n    public void findFilename_securityIssue() throws FileDownloadSecurityException {\n        final FileDownloadConnection connection = mock(FileDownloadConnection.class);\n        when(connection.getResponseHeaderField(\"Content-Disposition\"))\n                .thenReturn(\"attachment; filename=\\\"../abc\\\"\");\n\n        thrown.expect(FileDownloadSecurityException.class);\n        FileDownloadUtils.findFilename(connection, \"url\");\n\n        thrown.expect(FileDownloadSecurityException.class);\n        when(connection.getResponseHeaderField(\"Content-Disposition\"))\n                .thenReturn(\"attachment; filename=\\\"a/b/../abc\\\"\");\n        FileDownloadUtils.findFilename(connection, \"url\");\n\n        when(connection.getResponseHeaderField(\"Content-Disposition\"))\n                .thenReturn(\"attachment; filename=\\\"/abc/adb\\\"\");\n        assertThat(FileDownloadUtils.findFilename(connection, \"url\")).isEqualTo(\"/abc/adb\");\n    }\n\n    @Test\n    public void getFileNameFromUrl() {\n        String url = \"http://mirror.internode.on.net/pub/test/5meg.test5\";\n        assertThat(FileDownloadUtils.findFileNameFromUrl(url)).isEqualTo(\"5meg.test5\");\n\n        url = \"http://cdn-l.llsapp.com/connett/25183b40-22f2-0133-6e99-029df5130f9e\";\n        assertThat(FileDownloadUtils.findFileNameFromUrl(url))\n                .isEqualTo(\"25183b40-22f2-0133-6e99-029df5130f9e\");\n\n        url = \"http://www.httpwatch.com/httpgallery/chunked/chunkedimage.aspx?0.04400023248109086\";\n        assertThat(FileDownloadUtils.findFileNameFromUrl(url)).isEqualTo(\"chunkedimage.aspx\");\n\n        url = \"http://113.207.16.84/dd.myapp.com/16891/2E53C25B6BC55D3330AB85A1B7B57485.apk?mkey=\"\n                + \"5630b43973f537cf&f=cf87&fsname=com.htshuo.htsg_3.0.1_49.apk&asr=02f1&p=.apk\";\n        assertThat(FileDownloadUtils.findFileNameFromUrl(url))\n                .isEqualTo(\"2E53C25B6BC55D3330AB85A1B7B57485.apk\");\n\n        url = \"\";\n        assertThat(FileDownloadUtils.findFileNameFromUrl(url)).isNull();\n\n        assertThat(FileDownloadUtils.findFileNameFromUrl(null)).isNull();\n\n        assertThat(FileDownloadUtils.findFileNameFromUrl(\"abc\")).isNull();\n    }\n}"
  },
  {
    "path": "library/src/test/resources/mockito-extensions/org.mockito.plugins.MockMaker",
    "content": "mock-maker-inline"
  },
  {
    "path": "okcat.yml",
    "content": "# you can use https://github.com/Jacksgong/okcat for debug filedownloader more gentle\n# we will filter out logs with the provided package (name)\n# this 'package' keyword is just using for android adb logcat\npackage: com.liulishuo.filedownloader.demo\n\n# this 'log-line-regex' is just a regex for one line log\n# now we support keyword: 'data' 'time' 'level' 'tag' 'process' 'thread' 'message'\n# you don't have to provide all keyword, but you have to provide at least the 'message'\n# such as: 'message=\"(\\S*)\"'\n# log-line-regex: 'data,time,level,tag,process,thread,message = \"(.\\S*) (.\\S*) ([A-Z])/([^:[]*):\\[(\\d*):([^] ]*)\\] (.*?)$\"'\n\n# on the case of filter logs from Android adb logcat, we using 'adb logcat -v brief -v threadtime' command to obtain logcat\n# in the normal case you don't need ot provide this config, because there is a perfect one on the okcat internal\n# but if you want to customize the regex log from adb logcat, it's free to define it such below\n# adb-log-line-regex: 'data,time,process,thread,level,tag,message=\"(.\\S*) (.\\S*) (\\d*) (\\d*) ([A-Z]) ([^:]*): (.*?)$\"'\n\n# separator regex list\n# you can provide multiple regex to separate serial logs\nseparator-regex-list: \n  # on this case, if one line log match 'call start Url\\[([^\\]]*)\\]' regex we will separate logs with \\n and output a indie line with the '([^\\]]*)' value as the title of separate\n  - 'call start Url\\[([^\\]]*)\\]'\n\n# tag keyword list\n# this list keyword is using for filter out which log need to be output\n# all provided keyword will be using for compare with each line tag, if a line with tag not contain any keyword on 'tag-keyword-list' it will be ignore to output\ntag-keyword-list:\n  - FileDownloader\n\n# translate message map\n# if a message on a line start with provide keyword on the 'trans-msg-map' we will add the value of the keyword on the start of the message, and the word of value will be corlored to highlight it\ntrans-msg-map:\n  # such as this case:\n  # origin message: 'filedownloader:lifecycle:over xxx'\n  # after translate: '| Task OVER | filedownloader:lifecycle:over xxx'\n  'filedownloader:lifecycle:over': 'Task OVER'\n  'fetch data with': 'Start Fetch'\n\n# translate tag map\n# if a tag on a line contain provide keyword on the 'trans-tag-map' we will add the value of the keyword on the start of the message, and the background of the value word will be corlored to highlight it\ntrans-tag-map:\n  # such as this case:\n  # origin message: 'FileDownloadApplication  xxx'\n  # after translate: 'FileDownloadApplication [Thread Change] xxx'\n  'FileDownloadApplication': '[Thread Change]'\n  'DownloadTaskHunter': '[Status Change]'\n  'ConnectTask': '[Request]'\n\n# hide message list\n# if a message on a line start with provide value on the 'hide-msg-list` and the length of the message is less than 100 word, it would be colored with gray to hide\nhide-msg-list:\n  # here we hide message start with 'notify progress' and '~~~callback' because it is too frequently to output and useless in most case\n  - 'notify progress'\n  - '~~~callback'\n\n# highlight list\n# if any value on the 'hightlist-list' display on any message, the background of the value word would be colored to highlight it\nhighlight-list:\n  - 'Path['\n  - 'Url['\n  - 'Tag['\n  - 'range['\n"
  },
  {
    "path": "settings.gradle",
    "content": "include ':demo', ':library'\n"
  }
]